news 2026/6/15 17:08:09

二分查找进阶指南:从 “找一个数” 到 “锁定左右边界”,逻辑因果与代码实现全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二分查找进阶指南:从 “找一个数” 到 “锁定左右边界”,逻辑因果与代码实现全解析

详细解释可以看这篇文章https://www.cnblogs.com/labuladong/p/12320448.html。

一、基础框架:二分查找通用模板

给出二分查找的核心框架,明确关键可变细节(用...标记),并强调“用else if替代else”以清晰展现逻辑:

intbinarySearch(int[]nums,inttarget){intleft=0,right=...;// 细节1:right初始化while(...){// 细节2:循环终止条件intmid=(left+right)/2;if(nums[mid]==target){...}// 细节3:找到目标后的处理elseif(nums[mid]<target){left=...}// 细节4:左边界更新elseif(nums[mid]>target){right=...}// 细节5:右边界更新}return...;// 细节6:最终返回值}

注:暂忽略mid计算的溢出问题(提及可参考前文解决)。

二、三类典型场景:细节拆解与逻辑分析

网页分三类场景,逐一解析“right初始化、循环条件、边界更新、返回值”等细节的差异及原因,核心是围绕“搜索区间定义”展开逻辑推导。

1. 场景1:寻找一个数(基本二分搜索)

  • 功能:搜索目标值,存在则返回索引,否则返回-1。
  • 核心代码
    intbinarySearch(int[]nums,inttarget){intleft=0;intright=nums.length-1;// 搜索区间:[left, right](两端闭)while(left<=right){// 终止条件:left = right + 1(区间为空,无遗漏)intmid=(left+right)/2;if(nums[mid]==target)returnmid;// 找到即返回(仅需一个目标)elseif(nums[mid]<target)left=mid+1;// 排除mid,新区间[mid+1, right]elseif(nums[mid]>target)right=mid-1;// 排除mid,新区间[left, mid-1]}return-1;// 区间为空,未找到}
  • 关键细节解释
    • 为何while(left <= right)?因搜索区间是[left, right],终止时区间为空(如[3,2]),无遗漏;若用<,终止时区间为[left, left](非空),可能漏掉目标。
    • 为何left=mid+1/right=mid-1?因mid已验证非目标,需从搜索区间中排除。
  • 缺陷:无法找到目标的“左侧边界”或“右侧边界”(如[1,2,2,2,3]中target=2,无法返回索引1或3)。
    实例
    输入
    数组 nums = [1, 3, 5, 7, 9, 11],目标值 target = 7(若目标不存在:target = 4,预期返回 - 1)
    分步推导(以找 7 为例)
    初始化:搜索区间 [left, right] = [0, 5](闭区间,覆盖所有元素)
    第一次循环:
    mid = (0+5)/2 = 2,nums[2] = 5
    因 5 < 7,排除左半区(mid 已验证非目标),更新 left = mid+1 = 3,新区间 [3,5]
    第二次循环:
    mid = (3+5)/2 = 4,nums[4] = 9
    因 9 > 7,排除右半区,更新 right = mid-1 = 3,新区间 [3,3]
    第三次循环:
    满足 left <= right(3<=3),mid = 3,nums[3] = 7
    找到目标,直接返回 mid = 3(符合预期)
    若目标不存在(找 4)
    初始区间 [0,5],第一次循环 mid=2(5>4),right=1,区间 [0,1]
    第二次循环 mid=0(1<4),left=1,区间 [1,1]
    第三次循环 mid=1(3<4),left=2,区间 [2,1](空区间)
    循环终止,返回 -1(符合预期)

2. 场景2:寻找左侧边界(如target=2返回最左索引1)

  • 功能:找到目标值的最左侧索引,若无则返回-1(返回值也可理解为“小于target的元素个数”)。
  • 核心代码
    intleft_bound(int[]nums,inttarget){if(nums.length==0)return-1;intleft=0;intright=nums.length;// 搜索区间:[left, right)(左闭右开)while(left<right){// 终止条件:left = right(区间为空,无遗漏)intmid=(left+right)/2;if(nums[mid]==target)right=mid;// 不返回,收缩右边界(锁定左侧)elseif(nums[mid]<target)left=mid+1;// 新区间[mid+1, right)elseif(nums[mid]>target)right=mid;// 新区间[left, mid)}// 补全“未找到”判断:left范围是[0, nums.length]if(left==nums.length)return-1;// target比所有数大returnnums[left]==target?left:-1;// 验证是否为目标}
  • 关键细节解释
    • 为何right = nums.length?因搜索区间是[left, right)rightnums.length可覆盖“target比所有数大”的场景(如返回4表示小于target的元素有4个)。
    • 为何while(left < right)?终止时left=right,区间[left, left)为空,无遗漏。
    • 为何nums[mid]==targetright=mid?不立即返回,而是缩小右边界,在[left, mid)中继续搜索,锁定最左侧目标。
    • 为何返回left?终止时left=right,二者等价。

3. 场景3:寻找右侧边界(如target=2返回最右索引3)

  • 功能:找到目标值的最右侧索引,若无则返回-1。
  • 核心代码
    intright_bound(int[]nums,inttarget){if(nums.length==0)return-1;intleft=0,right=nums.length;// 搜索区间:[left, right)(左闭右开)while(left<right){intmid=(left+right)/2;if(nums[mid]==target)left=mid+1;// 不返回,收缩左边界(锁定右侧)elseif(nums[mid]<target)left=mid+1;// 新区间[mid+1, right)elseif(nums[mid]>target)right=mid;// 新区间[left, mid)}// 补全“未找到”判断:left范围是[0, nums.length]if(left==0)return-1;// target比所有数小returnnums[left-1]==target?(left-1):-1;// 需减1(因left已超右侧边界)}
  • 关键细节解释
    • 为何nums[mid]==targetleft=mid+1?不立即返回,缩小左边界,在[mid+1, right)中继续搜索,锁定最右侧目标。
    • 为何返回left-1?因left更新为mid+1,终止时nums[left]必非目标,而nums[left-1]可能是目标(如[1,2,2,2,3]中,找到最后一个2后left会指向4,需减1得3)。

三、核心总结:细节差异的因果逻辑

三类场景的细节差异,本质由“搜索区间定义”决定,形成如下因果链:

场景搜索区间right初始化while循环条件找到target后的操作边界更新方式返回值处理
寻找一个数[left, right]nums.length - 1left <= right立即返回midleft=mid+1/right=mid-1未找到返回-1
寻找左侧边界[left, right)nums.lengthleft < rightright=mid(收缩右边界)left=mid+1/right=mid未找到返回-1,找到返回left
寻找右侧边界[left, right)nums.lengthleft < rightleft=mid+1(收缩左边界)left=mid+1/right=mid未找到返回-1,找到返回left-1

实例解析与代码验证

二分查找的“细节魔鬼”体现在搜索区间定义上,不同场景对应不同的区间逻辑。以下通过「具体需求+输入输出+分步推导」,为三种场景逐一举例,帮你直观理解边界处理的本质。

场景1:寻找一个数(基本二分搜索)

核心需求

有序无重复/有重复数组中,判断目标值是否存在,存在则返回任意一个目标值的索引,不存在则返回-1(仅需“找到与否”,不关心目标的位置范围)。

实例

输入

数组nums = [1, 3, 5, 7, 9, 11],目标值target = 7
(若目标不存在:target = 4,预期返回-1)

分步推导(以找7为例)
  1. 初始化:搜索区间[left, right] = [0, 5](闭区间,覆盖所有元素)
  2. 第一次循环
    mid = (0+5)/2 = 2nums[2] = 5
    5 < 7,排除左半区(mid已验证非目标),更新left = mid+1 = 3,新区间[3,5]
  3. 第二次循环
    mid = (3+5)/2 = 4nums[4] = 9
    9 > 7,排除右半区,更新right = mid-1 = 3,新区间[3,3]
  4. 第三次循环
    满足left <= right(3<=3),mid = 3nums[3] = 7
    找到目标,直接返回mid = 3(符合预期)
若目标不存在(找4)
  1. 初始区间[0,5],第一次循环mid=2(5>4),right=1,区间[0,1]
  2. 第二次循环mid=0(1<4),left=1,区间[1,1]
  3. 第三次循环mid=1(3<4),left=2,区间[2,1](空区间)
  4. 循环终止,返回-1(符合预期)

场景2:寻找左侧边界

核心需求

有序有重复数组中,找到目标值的「最左侧索引」(即第一个等于目标的元素位置);若目标不存在,返回-1(需“锁定目标的起始范围”,如统计目标出现次数时需先找左边界)。

实例

输入

数组nums = [1, 2, 2, 2, 3, 4],目标值target = 2
(若目标不存在:target = 5,预期返回-1)

分步推导(以找2的左边界为例)
  1. 初始化:搜索区间[left, right) = [0, 6)(左闭右开,right取数组长度,覆盖“目标比所有数大”的场景)
  2. 第一次循环
    mid = (0+6)/2 = 3nums[3] = 2
    找到目标但不返回,需收缩右边界锁定左半区,更新right = mid = 3,新区间[0,3)
  3. 第二次循环
    mid = (0+3)/2 = 1nums[1] = 2
    继续收缩右边界,更新right = mid = 1,新区间[0,1)
  4. 第三次循环
    mid = (0+1)/2 = 0nums[0] = 1
    1 < 2,排除左半区,更新left = mid+1 = 1,新区间[1,1)(空区间)
  5. 验证返回
    left = 1nums[1] = 2,返回1(即2的最左侧索引,符合预期)
若目标不存在(找5)
  1. 初始区间[0,6),第一次循环mid=3(2<5),left=4,区间[4,6)
  2. 第二次循环mid=5(4<5),left=6,区间[6,6)(空区间)
  3. 验证返回:left = 6(等于数组长度),返回-1(符合预期)

场景3:寻找右侧边界

核心需求

有序有重复数组中,找到目标值的「最右侧索引」(即最后一个等于目标的元素位置);若目标不存在,返回-1(与左边界配合可计算目标出现次数:右边界 - 左边界 + 1)。

实例

输入

数组nums = [1, 2, 2, 2, 3, 4],目标值target = 2
(若目标不存在:target = 0,预期返回-1)

分步推导(以找2的右边界为例)
  1. 初始化:搜索区间[left, right) = [0, 6)(左闭右开,同左边界场景)
  2. 第一次循环
    mid = (0+6)/2 = 3nums[3] = 2
    找到目标但不返回,需收缩左边界锁定右半区,更新left = mid+1 = 4,新区间[4,6)
  3. 第二次循环
    mid = (4+6)/2 = 5nums[5] = 4
    4 > 2,排除右半区,更新right = mid = 5,新区间[4,5)
  4. 第三次循环
    mid = (4+5)/2 = 4nums[4] = 3
    3 > 2,排除右半区,更新right = mid = 4,新区间[4,4)(空区间)
  5. 验证返回
    left = 4,需检查left-1 = 3(因left已超右边界),nums[3] = 2,返回3(即2的最右侧索引,符合预期)
若目标不存在(找0)
  1. 初始区间[0,6),第一次循环mid=3(2>0),right=3,区间[0,3)
  2. 第二次循环mid=1(2>0),right=1,区间[0,1)
  3. 第三次循环mid=0(1>0),right=0,区间[0,0)(空区间)
  4. 验证返回:left = 0(等于0),返回-1(符合预期)

三种场景实例对比总结

场景实例输入目标值预期输出核心差异(实例中体现)
寻找一个数[1,3,5,7,9,11]73找到目标立即返回,区间是闭区间[left, right]
寻找左侧边界[1,2,2,2,3,4]21找到目标不返回,收缩右边界,返回left
寻找右侧边界[1,2,2,2,3,4]23找到目标不返回,收缩左边界,返回left-1
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 13:51:50

AI泛舆情智能体协同平台:让数据学会“分工协作”

在信息爆炸的时代&#xff0c;舆情早已突破单一平台边界&#xff0c;散落在社交、短视频、新闻、论坛等各类渠道。传统舆情工具靠关键词检索、人工筛选&#xff0c;不仅效率低下&#xff0c;还易遗漏潜在风险。AI泛舆情智能体协同平台的出现&#xff0c;本质是用“专业分工协同…

作者头像 李华
网站建设 2026/6/15 1:21:10

全网最全8个一键生成论文工具,本科生轻松搞定论文格式!

全网最全8个一键生成论文工具&#xff0c;本科生轻松搞定论文格式&#xff01; AI 工具如何让论文写作变得轻松高效 在当前的学术环境中&#xff0c;越来越多的学生开始借助 AI 工具来提升论文写作的效率。无论是从内容生成、格式调整&#xff0c;还是降重处理&#xff0c;这些…

作者头像 李华
网站建设 2026/6/15 14:16:51

不卖的天价胸罩:维多利亚的秘密的 “营销核武器”

为什么维多利亚的秘密要提供价值数百万美元的镶钻胸罩&#xff0c;即便从来没人买过&#xff1f;不卖的天价胸罩&#xff1a;维多利亚的秘密的 “营销核武器”维多利亚的秘密每年推出价值数百万美元的镶钻胸罩&#xff08;Fantasy Bra&#xff09;&#xff0c;却从未真正售出&a…

作者头像 李华
网站建设 2026/6/15 14:17:45

【2026最新 架构环境安装篇二】Docker安装MySQL8详细教程

#拉取MySQL镜像 docker pull mysql:8.0#创建本地目录&#xff08;用于挂载数据、配置&#xff09; mkdir -p ~/docker/mysql/data ~/docker/mysql/conf#启动容器&#xff08;挂载目录配置&#xff09; docker run -d \ --name mysql8 \ -p 3306:3306 \ -v ~/docker/mysql/data:…

作者头像 李华
网站建设 2026/6/15 14:18:16

先画个重点:这套PMSM双闭环方案里,外环MPC负责速度控制,内环无差拍处理电流跟踪。咱们直接上硬货,看看怎么用Simulink把这套算法落地

基于扰动观测器的永磁同步电机&#xff08;PMSM&#xff09;模型预测控制&#xff08;MPC&#xff09;仿真&#xff0c;速度外环基于模型预测控制、电流内环基于无差拍控制搭建&#xff0c;控制效果理想&#xff0c;模块程序设计通俗易通&#xff0c;送参考文献&#xff0c;方便…

作者头像 李华
网站建设 2026/6/14 15:25:03

golang 项目依赖备份

依赖存放路径&#xff1a;C:\Users\CHHC\go\pkg\mod清空存放路径下的文件根据go.mod 和 go.sum 下载依赖go mod download打包文件

作者头像 李华