什么是二分查找
二分查找通俗讲就是在一个已经排好序的数组中找答案的范围,每次取范围的中间值。如果这个值大了,就取前一半继续缩小范围;如果这个值小了,就取后一半缩小范围。
经过每一次都可以缩小1/2的范围,逐渐得到答案。
错误边界样例
例题:https://www.acwing.com/problem/content/description/1229/
这题很明显可以用二分,枚举1到100000的情况
设l=1,r=1e5,mid=(l+r)/2;
以前我一直都是如果mid小了就l=mid+1;mid大了就r=mid-1,但是当这题答案为5时,就会答案错误,为什么呢?
如图,每行第一个为l,第二个为r,第三个为mid
如果此题答案为5,当mid=4时,l=mid+1,即l=5,就退出了循环,最后答案为4。
边界
(1)mid=(l+r)/2
时,其实是(l+r)/2的向下取整。当l=mid,r=mid+1时,如果l+1直接退出循环,而r=mid+1这个值都没有取到过。
由于是向下取整,即l <= mid < r
,所以mid始终是取不到r的值,可以改成mid大了就r=mid,即
while(l<r){
int mid = (l+r)/2;
判断成立语句;
if(满足条件){
ans=mid;
l=mid+1;
}
else{
r=mid;
}
}
(2)也可以向上取整,即mid=(l+r+1)/2
。同理,l < mid <= r
mid始终不会等于l,这时可以改成mid小了就l=mid,可得代码
while(l<r){
int mid = (l+r+1)/2;
判断成立语句;
if(满足条件){
ans=mid;
l=mid;
}
else{
r=mid-1;
}
}
引申
如果在一串排好序的数组中,利用向上取整和向下取整可以返回一串数组中某个数字的首次出现的位置和末次出现的位置。
首次出现位置即lower_bound可用如下代码实现:
void f(int num){
int i=1, j=n;
while(i != j){
int mid = (i+j)/2;
if(a[mid]>=num) j = mid;
else i = mid + 1;
}
if(i==j && a[i]==num) cout<<i-1<<" ";
else cout<<"-1 ";
}
末次出现位置即upper_bound可用如下代码实现:
void f1(int num){
int i=1, j=n;
while(i!=j){
int mid = (i+j+1)/2;
if(a[mid]>num) j = mid - 1;
else i = mid ;
}
if(a[i]==num) cout<<i-1<<" ";
else cout <<"-1/n";
}