整数型二分查找详解
整数型二分一共有两个,分别适用于不同的情况。
版本1:你要找的数字在右边
int binary_search(int l,int r)
{
while(l < r)
{
int mid = l + r + 1 >> 1;
if(check(mid))
{
l = mid;
}
else
{
r = mid - 1;
}
}
return l;
}
举一个例子:数组:1 2 2 2 3 4 六个数 查找3(3 是偏靠右边的数字 上帝视角)
那么mid = (0 + n - 1 + 1) / 2 = (0 + 6) / 2 = 3 a[3] = 2
2 < 3 那么很显然,需要将l = mid 意思就是:这个数组是有序的,mid位子上的数字小于你要查找的数字,那么下一轮查找就要让左边界l = mid 反正mid左边的那些数字都小于3,这是缩小范围的过程!,
然后l = mid = 3, mid = (3 + 5 )/ 2 = 4 a[mid] = 3 找到。
同时你也可以认为模板二是从右往左查找第一个小于等于x。
版本2:你要找的数字在左边
int binary_search(int l,int r)
{
while(l < r>
{
int mid = l + r >> 1;
if(check(mid))
{
r = mid;
}
else
{
l = mid + 1;
}
})
}
举一个例子:数组 1 2 3 3 4 5 查找2
那么mid = (0 + 5) / 2 = 2 a[mid] = a[2] = 3
a[mid] > 2 对吧,所以,由于这个数组是有序的,你要查找的数字一定在左边,所以你为了缩小范围,就需要让r = mid = 2 下一步就找到数值2
同时你也可以认为模板二是从左往右查找第一个大于等于x
问题一、那么到底该如何选取模板?
我们只需要使用mid位上的数值与需要查找的数字进行比较即可,如果x > a[mid],说明你要查找的数字在右边,那么就是用模板一,如果你要查找的数字在左边(x < a[mid]),那么你就使用模板2
问题二、为什么模板一需要使用mid = l + r + 1 >> 2 但是模板二没有?
举一个例子,如果数组只有两个数3 5,l与r只差一,l = 0 r = 1,查找数字5
那么 假设我要查找右边的数字1 那么不加1,mid = l + r >> 2 mid = 0
a[mid] = 3 < 5 说明 你要查找的数字在右边, 对吧, 然后,你就需要更新l = mid = 0,
l = 0, 那么这不等于没变化吗? 所以我们需要加上1,才可以
那么,问题来了,模板二就没有这种情况吗?
同样的例子,数组只有两个元素3 5,那么你要查找数字3
mid = (0 + 1) / 2 = 0,a[0] = 3 查找成功。
问题三、如果找不到数字是啥情况?
对于模板二,数组 1 1 3 3 4 5 查找数字 2
那么第一步,mid = (0 + 5) / 2 = 2 a[2] = 3 > 2 那么你要找的数字在左边,所以你也不需要写成mid = l + r + 1 >> 2,那么 r = mid (缩小范围),之后,mid = (0 + 2) / 2 = 1 a[1] = 1,那么a[mid] < 2,则 l = mid + 1 = 2,这就造成了r = l = 2, a[r] = a[l] = 3 > 2,那么它将永远也找不到数字2,可以看到这也应证了上面的一句话,模板二是从左往右找第一个大于或者等于x的数字,只不过,你可以理解为:找得到话:是正好等于x,找不到的话:是大于x
看一道例题:789.数的范围
AC代码:
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n,m;
int q[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0; i < n; i++)
{
scanf("%d",&q[i]);
}
while(m--)
{
int x;
scanf("%d",&x);
int l = 0,r = n -1;
while(l < r)
{
// 认为你要查找的数字在左边 那么使用模板二
int mid = l + r >> 1;
if(q[mid] >= x)
{
r = mid;
}
else
{
l = mid + 1;
}
}
if(q[l] != x)
{
//说明没找到,并且q[l] 是第一个大于x的数字
cout<<"-1 -1"<<endl;
}
else
{
cout<<l<<" "; // 查找到数字
// 但是使用模板二是从左往右查找的第一个大于或者等于x的数字 偏向于x
// 这只从左边找到一个数字,但是 这道题要查找的数字是多个的
// 这就意味着我还要从右往左在查找一次数字 这样就找到一个数字的下标范围
int l = 0, r = n - 1;
while(l < r)
{
int mid = l + r + 1>> 1;// 第二个模板 加一!
if(q[mid] <= x)
{
l = mid;
}
else{
r = mid - 1;
}
}
cout<<l<<endl; // 这里输出的l 是从右往左找第一个小于或者等于x的数字
}
}
return 0;
}