二分查找的细则(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。 这样应对的话一般是不会出错的。
相关推荐
CV-Climber1 小时前
检索技术的实际应用
人工智能·算法
hhzz2 小时前
Tiger AI Platform平台中增加人脸识别功能
图像处理·人工智能·算法·计算机视觉·大模型
从零开始的代码生活_2 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法
TsingtaoAI3 小时前
3D高斯泼溅技术发展及其在具身智能领域的应用综述
人工智能·算法·ai·具身智能·高斯泼溅
atunet3 小时前
关于图算法中的连通分量检测与最小割问题7
算法
心运软件4 小时前
银行客户流失预测(Python 完整实战)
算法
邪神与厨二病4 小时前
牛客周赛 Round 153
python·算法
qizayaoshuap6 小时前
# ❌ 井字棋 — 鸿蒙ArkTS Minimax AI算法与博弈系统设计
人工智能·算法·华为·harmonyos
皓月斯语6 小时前
B3842 [GESP202306 三级] 春游 题解
数据结构·c++·算法·题解
atunet6 小时前
树状结构在查询优化中的作用与实现细节7
算法