二分查找的细则(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。 这样应对的话一般是不会出错的。
相关推荐
shehuiyuelaiyuehao9 小时前
算法14,滑动窗口,找到字符串中所有字母异位词
算法
凯瑟琳.奥古斯特9 小时前
图论核心考点精讲
开发语言·数据结构·算法·排序算法·哈希算法
WolfGang0073219 小时前
代码随想录算法训练营 Day49 | 图论 part07
算法·图论
啦啦啦_99999 小时前
案例之 逻辑回归_癌症预测
算法·机器学习·逻辑回归
StockTV9 小时前
韩国股票实时数据 KOSPI(主板)和 KOSDAQ(创业板)的实时行情、K 线及指数数据
java·开发语言·算法·php
byte轻骑兵9 小时前
【LE Audio】BASS精讲[5]: 状态特征解析,广播接收状态实时可视全流程
人工智能·算法·音视频·语音识别·le audio·低功耗音频
m0_629494739 小时前
LeetCode 热题 100-----13.最大子数组和
数据结构·算法·leetcode
0xR3lativ1ty9 小时前
大模型算法原理高频题解析
算法
故事还在继续吗9 小时前
STL 容器算法手册
开发语言·c++·算法
田梓燊9 小时前
力扣:94.二叉树的中序遍历
数据结构·算法·leetcode