目录:
一:C++STL中二分的三个函数及其使用方法
二:手写二分搜索的方式以及一些技巧
三:比较常见的边界问题
一:C++STL中二分的三个函数及其使用方法:
①binary_search(arr.begin(),arr.end(),value)在arr中的指定范围找value,存在则返回1,否则返回0 (bool)。
②lower_bound(arr.begin(),arr.end(),value)),在arr中找到一个适合插入value的位置,并且返回值是 第一次满足要求的元素地址,例如在1,3,4,2,5,7,8,6,2,2,1中,若要插入2,则第一次满足条件是在(4,2)之间,因此会返回2的地址。(返回偏后的地址)
③upper_bound(arr.begin(),arr.end(),value)),在arr中找到一个适合插入value的位置,并且返回值是 最后一次满足要求的元素地址,例如在1,3,4,2,5,7,8,6,2,2,1中,若要插入2,则最后一次满足条件是在(2,1)之间,因此回返回最后一个元素(1)的地址。(返回偏后的地址)
TIP:
① 如何通过lower_bound或者upper_bound找到符合条件的元素的下标索引?
通过lower_bound()-arr即可得到下标值。
②如何通过lower_bound与upper_bound的组合找到某个元素在原数组中出现的次数?
第一步:首先对原数组进行sort排序。
第二步:lower_bound(arr.begin(),arr.end(),value))---upper_bound(arr.begin(),arr.end(),value))即为答案。
二:手写二分搜索的方式以及一些技巧:
①记录答案法:
int n, l, r, ans;
bool check(int x)
{
//判断函数
}
int main()
{
l = 0, r = 100;
while (l <= r) //记录答案法
{
int mid = (l + r) >> 1;
if (check(mid))
{
ans = mid;
r = mid;
}
else l = mid + 1;
}
return 0;
}
注意这里的l<=r中的"=",即while结束后,l会大于r。当我们二分的答案mid正确时,每次都视其为最优解(ans=mid)。这种记录答案的办法是为了避免当r和l相等的时候,相连r(或l)的情况。
我们不断放小l和r,此时假设l=2,r=3,并假设mid=4时答案成立。此时来到mid=(2+3)/2,即5/2,经过向下取整后,mid=2,答案不成立,因此l会来到3,再做一次取mid操作,此时mid=3,答案不成立。假设我们没有ans=mid的操作,那么这次循环结束后,r还在3,l走到了4。此时我们无法得知l和r哪一个是答案,这就是答案相连的情况。
除此之外,我们来看为什么l<=r而不是l<r。我们再做一次假设,与上述假设差不多,假设l=2,r=3,并且mid=3时答案成立,此时while的的条件为while(l<r)。 取mid之后,mid=2,答案不成立,l来到3,此时循环结束,可是这时候取mid=3的时候答案是成立的,因此如果l=r时循环已经退出了,有可能会漏掉一些答案。
简单来说,当l和r的处理均为mid+(-)1时,循环条件为l<=r。
②不记录答案,r就是最优解:
int n, l, r;
bool check(int x)
{
//判断函数
}
int main()
{
l = 0, r = 100;
while (l < r) //不记录答案,r就是最优解
{
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return 0;
}
每次mid为答案时,r就=mid,那么mid就是答案。不断放小l和r,假设某个时刻l=2,r=3,并且mid=2是不是答案。此时r=3是上一个答案,在这次循环中,mid=2,不是答案,l走到3,即l=r,循环退出,当循环结束之后,r就是答案了。(注:此时l=r,因此l也是答案,但是我们按照代码中r=mid作为标准)
注意:为什么l<r时,不能写成r=mid-1?
原因是当我们没有记录当前答案时,如果mid为答案,我们取r=mid-1会使得原答案消失。
三:比较常见的边界问题(导致死循环):
有时,我们写出来的二分,往往会不注意l而导致死循环。下面我们来看看这种会死循环的代码:
int n, l, r;
bool check(int x)
{
//判断函数
}
int main()
{
l = 0, r = 100;
while (l < r) //用二分找到最大的解
{
int mid = (l + r) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return 0;
}
这种求最大的二分过程,死循环的原因出现在l的取值。我们知道,每次二分以后,l和r之间必须有一个要发生变化,同样,我们不断放小l和r来看看这种极端情况。假设l=2,r=3,并且mid=2时,答案成立。
由于这次循环中,(l+r)>>1==5/2,经过向下取整后得到mid=2,而mid=2时答案成立,因此l=mid,注意,看到这里是不是发现问题了,从这个时候开始,l一直卡在2这个值不会变,而r也一直卡在3这里,因此l<r永远成立,死循环了。
那么,我们如何解决这种问题呢? 很简单,我们只要不让计算机向下取整就可以避免了,我们让计算机向上取整,这里有两种方法:
①利用c++的函数ceil(value) ,这里value==(l+r)>>1。如果计算的结果为小数,ceil每次取更大的整数,例如:当value==1.3时,ceil会取2,value==1时,ceil取1,value==-1.3时,ceil取-1。
因此我们每次循环中的mid的取值部分写成 int mid=ceil((l+r)>>1)即可。
②人工向上取整,直接写成int mid=(l+r+1)>>1。 这里的证明部分就不多说了。
补充:这种写法最后l就是答案。