二分查找是常见的算法,但是我发现我经常无法区分应当使用哪种变形来计算答案,因此这里做一下讲解.

标准写法

1
2
3
4
5
6
ll l=___,r=___;
while(l<=r){
ll mid=l+(r-l)>>1;
if(____) l=mid+1;
else r=mid-1;
}

判断条件的选择

判断条件可以理解为,每次要淘汰哪一部分,并通过l,r指针的移动来确定的

前提条件:假设现在我们有一个升序的数组

比如:我现在要查找第一个大于等于key的值
那么我们的条件就应该是每次循环都删去小于key的值,不能删去大于的,因为答案可能在这里面

于是我们的条件就写成:if(a[mid]<key) l=mid+1;
为什么是mid+1?因为l表示的是左边界,左边界移动了,就相当于淘汰了左边的小于key的值

同理,我们在写找第一个大于当前值时,只要改成<=即可

那么反过来呢?

没错,把条件和操作全反过来即可.现在我们要找第一个小于等于key的值
所以我们每次淘汰的是大于key的值

条件变为:if(a[mid]>key) r=mid-1;

答案的取值

弄清楚了怎么写条件,我们还需要弄清楚最后l还是r指向的才是答案.

我的方法是,假设第一次指向的就是正确答案,那么哪个指针指向就是正确答案.
所以也是就if对应的变化的指针,就是答案.

比如查找大于等于key时,if条件后面变化的是l,所以答案就是l所对应的值.

其他技巧

学习二分主要的目的不是为了二分查找,而是为了二分答案.判断条件有可能会更复杂.但是核心还是上面哪两点,首先知道我要删的是什么,其次答案会等于什么位置

最后,使用二分时不要忘记检查数据是否以及排序,答案是否一定满足升序关系,再加上适量的边界检查,二分就能拿下啦!

C++自带的二分

C++中,能使用lower_bound和upper_bound的有set,multi_set(多重集合),数组,vector

前者查找第一个大于等于key的值,后者找第一个大于key的值(先保证数组有序才行)

那么小于或小于等于呢?

小于可以先查大于等于的,再向前一步
小于等于可以先查大于的,再向前一步