二分查找的细则(binary search)

二分查找的使用分类: 1)查找值,返回就行,没有其他要求 2)查找最左、最右边界的值(比如有重复的要求返回最左、最右下标)

下面从多个方面来进行分析:

1)终结条件:left <= right or left < right

首先我们要明白这两个的明确区别在哪里: 可以这么来说,二分查找的第一类使用 left <= right,第二类使用 left < right。Why?

接下来提供具体代码:

Normal (查找值):

Cpp

Copy

复制代码
vector nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10 };
int target = 7;
int left = 0;
int right = nums.size() - 1;
while(left <= right) {
    // 标准向下取整写法,防止溢出且清晰
    int mid = left + (right - left) / 2; 
    if(nums[mid] > target) 
        right = mid - 1;
    else if (nums[mid] < target) 
        left = mid + 1; // 【修正】原代码此处为 left = mid - 1,逻辑错误,应向右收缩
    else 
        return mid;
}

target 是我们要找的目标值。

Specific (查找边界): 但如果我们要查找到 target 对应 vector<int> nums 的最左下标或者最右下标,我们如果使用上面的模板就会出错(因为找不到确切边界),所以我们要使用第二个模板;

Cpp

Copy

复制代码
vector nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10 };
int target = 10;
// 要求找到最右下标
int left = 0;
int right = nums.size() - 1;
while(left < right) {
    // 【修正】这里必须向上取整,配合 left = mid 防止死循环
    int mid = left + (right - left + 1) / 2; 
    if(nums[mid] <= target) 
        left = mid; // 【修正】原代码 left = mid + 1 是找 upper_bound,此处改为找右边界逻辑
    else 
        right = mid - 1;
}
return left; // 此时 left == right,即为最右下标

2)取整方式

取整方式分为向上取整、向下取整。 这个在查找的第一种情况(left <= right)是随意的,因为每次边界都会移动 mid + 1mid - 1,不会卡住。但对应第二种情况(left < right)是有要求的,为什么?

下面以取最左下标为例: 如果我们想取到一个 target 最小下标,这就要 我们如果取到 target 时不能直接跳过该位置。 此时我们可以让 right = mid,也可以让 left = mid

  • 关键逻辑
    • 如果是 left = mid(左边界不移动),此时必须使用向上取整mid = left + (right - left + 1) / 2)。因为如果是向下取整,当 leftright 相邻时,mid 会等于 left,导致 left 不变,死循环。
    • 如果是 right = mid(右边界不移动),此时必须使用向下取整mid = left + (right - left) / 2)。因为如果是向上取整,当 leftright 相邻时,mid 会等于 right,导致 right 不变,死循环。

可以看到的是,对于取到了一个值和 target 相同的情况,是通过改变另一个边界的大小来逼近的。同理对于取最右下标,我们同样使用 left = mid(配合向上取整)或 right = mid(配合向下取整)来应对。

3)返回值

就像我们在上个例子举得一样,你的返回值同样是会决定我们返回的正确性的。 在 left < right 的循环中,循环结束时 left == right

  • 如果是找左边界,循环结束后 left 指向第一个 >= target 的位置。
  • 如果是找右边界,循环结束后 left 指向最后一个 <= target 的位置。 所以直接 return left 即可得到正确下标。

总结

我们在 left, right 是闭区间还是开区间,我个人偏爱闭区间 [left, right],因为对于开区间的话 right 是非法访问索引,这样的话就有很多地方都限制了我们。

对于一般情况(left <= right),无论是向上取整还是向下取整都是 OK 的。 但对于特殊情况(left < right 找边界):

  • 找最左下标 :使用 left < right + 向下取整 + right = mid
  • 找最右下标 :使用 left < right + 向上取整 + left = mid。 这样应对的话一般是不会出错的。
相关推荐
A923A1 小时前
【洛谷刷题 | 第三天】
算法·二分·洛谷·pair
abant22 小时前
leetcode912 排序算法总结
算法·leetcode·排序算法
@猿程序2 小时前
ShardingSphere自定义分片算法与Redis动态规则加载实战
网络·redis·算法
Share_Shun2 小时前
【定位引导】多点对位算法
算法
炽烈小老头2 小时前
【 每天学习一点算法 2026/03/18】全排列
学习·算法
Book思议-2 小时前
【数据结构实战】判断链表是否有环:快慢指针法(Floyd 判圈算法)
c语言·数据结构·算法·链表
liuyao_xianhui2 小时前
优选算法_位运算_只出现一次的数字3_C++
开发语言·数据结构·c++·算法·leetcode·链表·动态规划
lihao lihao2 小时前
滑动窗口
数据结构·算法
Jordannnnnnnn2 小时前
复试打卡day30
算法