二分查找及其变种

一、概念

二分查找算法(Binary Search Algorithm)是一种在有序数组中查找特定元素的高效搜索方法。

其基本思想是将目标值与数组中间的元素进行比较,如果目标值等于中间元素,则查找成功;如果目标值小于中间元素,则在数组左半部分继续查找;如果目标值大于中间元素,则在数组右半部分继续查找。这个过程将不断重复,直到找到目标值或搜索范围为空为止。

二、实现步骤

2.1 初始化: 确定搜索范围的左右边界,通常用两个指针表示,一个指向数组的起始位置(left,索引0),另一个指向数组的结束位置(right,数组长度减1)。

2.2 循环条件: 当左指针小于等于右指针时,继续查找。

2.3 计算中间索引: 计算当前搜索范围的中间位置 mid,通常使用 (left + right) / 2 来避免整数溢出。

口诀:奇数二分取中间 偶数二分取中间靠左;

2.4 比较中间元素: 比较 array[mid] 与目标值 target:

  • 如果 array[mid] 等于 target,则查找成功,返回 mid。
  • 如果 array[mid] 大于 target,则在左半部分继续查找,更新右指针 right = mid - 1。
  • 如果 array[mid] 小于 target,则在右半部分继续查找,更新左指针 left = mid + 1。

2.5 结束循环: 如果左指针大于右指针,表示搜索范围为空,目标值不在数组中,返回一个表示未找到的值,如 -1。

三、代码实现

public class BinarySearch {
    public int binarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2; // 防止溢出
            if (nums[mid] == target) {
                return mid; // 找到目标值,返回索引
            } else if (nums[mid] < target) {
                left = mid + 1; // 在右半部分查找
            } else {
                right = mid - 1; // 在左半部分查找
            }
        }
        
        return -1; // 未找到目标值,返回-1
    }
}

注意事项:

 1. 如果left 和 right比较大的话,两者之和就有可能会溢出。改进的方法是将 mid 的计算方式写成 left +(right-left )/2。更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 left +((right-left )>>1)。因为相比除法运算来说,计算机处理位运算要快得多。

2. while  (left < = right),注意括号内为 left <= right ,而不是 left < right ,如果我们设置条件为 left  <  right 则当我们执行到最后一步时,则我们的 left 和 right 重叠时,则会跳出循环,返回 -1,区间内不存在该元素,但是不是这样的,我们的 left 和 right 此时指向的就是我们的目标元素 ,但是此时 left = right 跳出循环

3. left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。当我们的target 元素为 16 时,然后我们此时 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果设置 left = mid 的话,则会进入死循环,mid  值一直为7 。

四、算法变种

4.1 

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

示例 1:
输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]

示例 2:
输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1]

示例 3:
输入:nums = [], target = 0 输出:[-1,-1]

从最原始的二分查找代码中分析, nums[mid] == target 时则返回,nums[mid] < target 时则移动左指针,在右区间进行查找, nums[mid]  >  target 时则移动右指针,在左区间内进行查找。

那么我们思考一下,如果此时我们的 nums[mid] = target ,但是我们不能确定 mid 是否为该目标数的左边界,所以此时我们不可以返回下标。

 

此时 mid = 4 ,nums[mid] = 5,但是此时的 mid 指向的并不是第一个 5,所以我们需要继续查找 ,因为我们要找的是数的下边界,所以我们需要在 mid 的值的左区间继续寻找 5 ,那应该怎么做呢?

我们只需在target <= nums[mid] 时,让 right = mid - 1即可,这样我们就可以继续在 mid 的左区间继续找 5 。

解决方案:

将小于和等于合并在一起处理,当 target <= nums[mid] 时,我们都移动右指针,也就是 right = mid -1,还有一个需要注意的就是,我们计算下边界时最后的返回值为 left ,当上图结束循环时,left = 3,right = 2,返回 left 刚好时我们的下边界。

计算上边界时算是和计算上边界时条件相反,

计算下边界时,当  target <= nums[mid]  时,right = mid -1;target > nums[mid] 时,left = mid + 1;

计算上边界时,当  target < nums[mid] 时,right = mid -1; target >= nums[mid] 时 left = mid + 1;刚好和计算下边界时条件相反,返回right。

public class Solution {
   
    public static int[] searchRange(int[] nums,int target){
        int upper = upperBound(nums, target);
        int low = lowerBound(nums, target);
        //不存在情况
        if (upper < low){
            return new int[]{-1,-1};
        }
        return new int[]{low,upper};
    }

    //计算下边界
    static int lowerBound(int[] nums,int target){
        int left = 0, right = nums.length - 1;
        while (left <= right){
            //计算mid
            int mid = left + ((right - left) >> 1);
            if (target <= nums[mid]){
                //当目标值小于等于nums[mid]时,继续在左区间检索,找到第一个数
                right = mid -1;
            }else if (target > nums[mid]){
                //目标值大于nums[mid]时,则在右区间继续检索,找到第一个等于目标值的数
                left = mid + 1;
            }
        }
        return left;
    }

    //计算上边界
    static int upperBound(int[] nums,int target){
        int left = 0,right = nums.length - 1;
        while (left <= right){
            int mid = left + ((right - left) >> 1);
            if (target >= nums[mid]){
                left = mid + 1;
            }else if (target < nums[mid]){
                right = mid - 1;
            }
        }
        return right;
    }
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/771744.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apache SeaTunnel社区首位学生Committer诞生!

采访对象 | 陈炳烨 采访人&编辑 | Debra Chen Apache SeaTunnel社区第一位学生Committer就此诞生&#xff01;这位来自西安交通大学软件工程专业的同学从较为简单的文档修改工作&#xff0c;逐步深入到代码层面&#xff0c;到最后独立负责开发模块&#xff0c;为Apache S…

事务底层与高可用原理

1.事务底层与高可用原理 事务的基础知识 mysql的事务分为显式事务和隐式事务 默认的事务是隐式事务 显式事务由我们自己控制事务的开启&#xff0c;提交&#xff0c;回滚等操作 show variables like autocommit; 事务基本语法 事务开始 1、begin 2、START TRANSACTION&…

【代码随想录】【算法训练营】【第55天】 [42]接雨水 [84]柱状图中最大的矩形

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 55&#xff0c;又是一个周一&#xff0c;不能再坚持~ 题目详情 [42] 接雨水 题目描述 42 接雨水 解题思路 前提&#xff1a;雨水形成的情况是凹的, 需要前中后3个元素&#xff0c;计算该元…

【AI】DeepStream(14):图像分割deepstream-segmentation-test示例演示

【AI】AI学习目录汇总 1、简介 deepstream-segmentation-test示例演示了图像的语义分割。两个配置文件,分别加载U-Net和Res-UNet两种分割模型 unet_output_graph.uffunetres18_v4_pruned0.65_800_data.uffU-Net是一个在生物医学图像分割领域广泛应用的卷积神经网络(CNN),…

中国东方资产管理25届秋招北森测评笔试如何高分通过?真题考点分析看完这篇就够了

一、东方资管校招测评题型分析 中国东方资产管理股份有限公司&#xff08;中国东方资管&#xff09;的校园招聘测评题型主要包括以下几个部分&#xff1a; 1. **计分题&#xff0c;行测知识**&#xff1a;这部分题量大约在56-57题左右&#xff0c;分为不同的模块进行计时测试。…

【高阶数据结构】图的应用--最短路径算法

文章目录 一、最短路径二、单源最短路径--Dijkstra算法三、单源最短路径--Bellman-Ford算法四、多源最短路径--Floyd-Warshall算法 一、最短路径 最短路径问题&#xff1a;从在带权有向图G中的某一顶点出发&#xff0c;找出一条通往另一顶点的最短路径&#xff0c;最短也就是沿…

14个最佳创业企业WordPress主题

您网站的设计使您能够展示产品的独特卖点。通过正确的主题&#xff0c;您将能够解释为什么客户应该选择您的品牌而不是其他品牌。 在本文中&#xff0c;我们将向您介绍14个初创企业WordPress主题。我们将告诉您每个主题的独特之处以及哪些人应该考虑使用它。让我们开始吧&…

Pinia:Vue 2 和 Vue 3 中更好用的状态管理框架

前言 还在用Vuex? 在Vue应用程序的开发过程中&#xff0c;高效且易于维护的状态管理一直是开发者关注的核心问题之一。随着Vue 3的发布&#xff0c;状态管理领域迎来了一位新星——Pinia&#xff0c;它不仅为Vue 3量身打造&#xff0c;同时也向下兼容Vue 2&#xff0c;以其简…

Django学习第四天

启动项目命令 python manage.py runserver 分页功能封装到类中去 封装的类的代码 """ 自定义的分页组件,以后如果想要使用这个分页组件&#xff0c;你需要做&#xff1a; def pretty_list(request):# 靓号列表data_dict {}search_data request.GET.get(q, &…

谷粒商城-个人笔记(集群部署篇二)

前言 ​学习视频&#xff1a;​Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强​学习文档&#xff1a; 谷粒商城-个人笔记(基础篇一)谷粒商城-个人笔记(基础篇二)谷粒商城-个人笔记(基础篇三)谷粒商城-个人笔记(高级篇一)谷粒商城-个…

Excel为数据绘制拆线图,并将均值线叠加在图上,以及整个过程的区域录屏python脚本

Excel为数据绘制拆线图,并将均值线叠加在图上,以及整个过程的区域录屏python脚本 1.演示动画A.视频B.gif动画 2.跟踪鼠标区域的录屏脚本 Excel中有一组数据,希望画出曲线,并且能把均值线也绘制在图上,以下动画演示了整个过程,并且提供了区域录屏脚本,原理如下: 为节约空间,避免…

SpringBoot 启动流程一

SpringBoot启动流程一 我们首先创建一个新的springboot工程 我们不添加任何依赖 查看一下pom文件 我们创建一个文本文档 记录我们的工作流程 我们需要的是通过打断点实现 我们首先看一下启动响应类 package com.bigdata1421.start_up;import org.springframework.boot.Spr…

【Android面试八股文】Android性能优化面试题:怎样检测函数执行是否卡顿?

文章目录 卡顿一、可重现的卡顿二、不可重现的卡顿第一种方案: 基于 Looper 的监控方法第二种方案:基于 Choreographer 的监控方法第三种方案:字节码插桩方式第四种方案: 使用 JVMTI 监听函数进入与退出总结相关大厂的方案ArgusAPMBlockCanaryQQ空间卡慢组件Matrix微信广研参…

linux中与网络有关的命令

本文的命令总览 ifconfig命令 在 Linux 系统中&#xff0c;ifconfig 命令用于配置和显示网络接口的信息&#xff0c;包括 IP 地址、MAC 地址、网络状态等。同时我们也可以利用ifconfig 命令设置网络接口对应的ip地址&#xff0c;子网掩码等 当你使用 ifconfig 命令时&#xf…

DC/AC电源模块为现代电子设备提供稳定的能源

BOSHIDA DC/AC电源模块为现代电子设备提供稳定的能源 DC/AC电源模块是一种重要的电子设备&#xff0c;它为现代电子设备提供稳定的能源。在今天的高科技社会中&#xff0c;电子设备已经成为人们生活和工作的重要组成部分。从家用电器到计算机、手机、汽车和航天航空设备&…

微信小程序毕业设计-球馆预约系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

Spring AI 1.0.0 新变化,从 0.8.1 如何升级

Spring AI 1.0.0-M1 版本已经发布&#xff0c;距离 1.0.0 正式版又更近了一步。同时这也意味着&#xff0c;Spring AI 1.0.0 的 API 已经基本确定&#xff0c;不会发生大的改动。这里介绍一下&#xff0c;相对于上一个发布版本 0.8.1&#xff0c;Spring AI 1.0.0 的一些重要的变…

【C语言】—— 文件操作(上)

【C语言】—— 文件操作&#xff08;上&#xff09; 一、 为什么使用文件二、 什么是文件2.1、 程序文件2.2、 数据文件2.3、 文件名2.4、二进制文件与文本文件 三、 文件的打开和关闭3.1、流和标准流&#xff08;1&#xff09;流&#xff08;2&#xff09;标准流 3.2、文件指针…

@PostConstruct注解

1.简介 PostConstruct是java5的时候引入的注解&#xff0c;主要用于标记一个方法&#xff0c;表示该方法应在依赖注入完成后自动调用。通常在使用Java EE或者Spring框架时使用这个注解&#xff0c;以便在Bean初始化之后执行一些初始化工作&#xff0c; 可作为一些数据的常规化…

hadoop集群部署【二】YARN MapReduce 的部署

提前注意&#xff1a;请注意路径是否和我的相同&#xff0c;放置的位置不同&#xff0c;请修改标红处 HDFS部署 HDFS介绍及部署http://t.csdnimg.cn/Q3H3Y 部署说明 Hadoop HDFS分布式文件系统&#xff0c;我们会启动&#xff1a; NameNode进程作为管理节点 DataNode进程…