我们以下讨论都是对于有序数组
整数二分确实比较难搞哈,我也是学了两边才差不多懂了y总的思路,写题的时候按照需求套模板就好了。
对于整数二分,我们可将其按照我们要找的数字x分为两段。
1. x是红色区间的右端点,将[L,R]分为[L,M-1]和[M,R]
//这个是思路1
while(L<R){
M=(L+R+1)/2;
if(M是红色) L=mid;
else R=mid-1;
}
//这个是模板1
while(L<R){
M=(L+R+1)/2;//注意这里如果不上取整的话,当区间只有两个数字的时候可能会陷入死循环
if(que[M]<=x) L=mid;
else R=mid-1;
}//最后找到的que[L]是小于等于x的第一个数字
2. x是绿色区间的左端点,将[L,R]分为[L,M]和[M+1,R]
//这个是思路2
while(L<R){
M=(L+R)/2;
if(M是绿色) R=mid;
else R=mid+1;
}
//这个是模板2
while(L<R){
M=(L+R)/2;
if(que[M]>=x) R=mid;
else L=mid+1;
}//最后找到的que[R]是大于等于x的第一个数字
写该类题目先判断我们想要的数字是什么类的。
比如这个计蒜客的题
Sample Input
3
2 5 8
2
10
5
Sample Output
8
5
这个题的思路就是我想找到 大于等于 这个要查询的数字的第一个下标(也就是绿色区域的左端点)。
也就是说判断条件的是
if(que[mid]>=x)
,注意这个括号里就是我们根据自己的需求填写的,这里要找大于等于x的第一个数字就是这个判断条件。对应代码如下:while(L<R){ mid=(L+R)/2; if(que[mid]>=x) R=mid;//注意这里是R=mid,上面更新mid的时候不用加一 else L=mid+1;
同样也可以寻找小于等于x的第一个下标
if(que[mid]<=x)
,对应代码如下:while(L<R){ mid=(L+R+1)/2; if(que[mid]<=x) L=mid;//注意这里是L=mid,上面更新mid的时候需要加一,否则陷入死循环 else R=mid-1;
我的解题代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int que[N];
int main(){
int n,m,x;
cin>>n;
for(int i=0;i<n;i++)
cin>>que[i];
cin>>m;
while(m--){
cin>>x;
int left=0;
int right=n-1;
while(left<right){
int mid=(left+right) >> 1;//因为这里是right=left所以是下取整
if(que[mid]>=x) {
right=mid;
}
else{
left=mid+1;
}
}//此时que[right]是大于等于x的第一个数
if(que[right]==x) cout<<que[right]<<endl;//说明数组中存在x
else {//说明此时que[right]是大于x的第一个数,那我们就要判断它和它左边的数字谁更接近x了
int iend;
if(right!=0)//判断right是不是数组边界,否则会出错
iend= abs(que[right]-x) < abs(que[right-1]-x) ? que[right]:que[right-1];
else
iend=que[right];
cout<<iend<<endl;
}
}
return 0;
}
相信自己!多写几个题就明白了。
先确定你要的数字是 ## 大于x ## 大于等于x ## 小于x ## 小于等于x
对应的if中判断条件分别为 ## que[mid]>x ## que[mid]>=x ## que[mid]<x ## que[mid]<=x
根据不同的if条件,我们写出对应的处理和else语句,if后面跟的直接就是需要更新的边界=mid;对应else后面才是mid-1或+1,然后再根据是否为right=mid,是的话mid的更新中不需要加1。得到的数字一定是满足这个if语句的第一个位置下标。
这样写可以避开所有的坑!
小数二分
while(r-l<1e-8){//在一个极小的区间
double mid=(l+r)/2;
if(mid>=x) r=mid;//这里我有点存疑,两个浮点数这么比较合理吗
else l=mid;
}