二分法的本质是不断划分区域,通过与要找的值比较大小,确定比这个值大的区域和小于等于这个值的区域,或是比这个值小的区域和大于等于这个值的区域。
这就取决于你是从左边找还是从右边找了
二分查找使用于有序数组
int bsearch_1(int l,int r){
while(l<r){
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}return l;
}
int bsearch_2(int l,int r){
while(l<r){
int mid=l+r+1>>1;
if(check(mid))l=mid;
else r=mid-1;
}return l;
}//如何记忆,存在mid-1的情况mid的初始值要取得大一些。
while括号里等号是用来确定最后要找的数的位置的。
acwing 789 数的范围
//折半查找肯定适用于有序数组
#include<iostream>
using namespace std;
const int N=100002;
int q[N];
//二分的两个模板
//整数二分
//二分的本质不一定是单调性
//二分的本质是边界
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
cin>>q[i];
//遇到输入一个执行一次,并输出结果的如何处理
//用while 循环
while(m--){
int x;
cin>>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){cout<<"-1 -1"<<endl; }
else {
cout<<l<<" ";
//再用二分查找找出最后一个等于x的值
//只需要接着上一个元素的基础上找就行
int 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;
}
}
return 0;
}
这两种模板的选择就是取决于你要找的数左边是都小于等于target,还是你要找的数右边都是大于target,
看你是从哪边开始想这个问题的,比如这个升序数组里有两个target,那么你一开始按照,这个target的右边的元素都比这个元素>=,那么你就能最终找到第一个target的位置,然后第二个就是从第一个target的位置开始往右找,所以你就得从此时的位置往右,那么你要找的target肯定比你左边的元素都大,此时用从左边找的模板nums[mid]<=target
,
如果你想都用从右边找,那么你寻找第二次的元素的l得+1,不能接着从已经找到的target 的位置再找了,否则还是找的第一个target。
但这个第二部分,只能从左往右找!!!
二分查找的应用
68. 0到n-1中缺失的数字
中心思想就是缺失的位置会由后面比他大的数来补上,那么也就是说,倘若当前的元素未缺失,那就说明左边的都未缺失,应该去右边寻找缺失的部分,l=mid+1;
否则就是当前这个位置r=mid
而且这个也不用考虑模板选择,就是找不着了得去右边找即l=mid+1;
chatgpt说的
如果 nums[mid] 不等于 mid,说明缺失的数字在 mid 的左边,更新 r 为 mid。
否则,说明缺失的数字在 mid 的右边,更新 l 为 mid+1。
这个题要考虑一个边界问题:
当n=1时,整个数组就是空的,此时用容器的判空操作
当缺少的是最后一个元素时,则存在缺失现象
就会让r指向最后一个元素,若仍匹配,那就让r+1
class Solution {
public:
int getMissingNumber(vector<int>& nums) {
//如果当前位置缺失则会让后面的补上这个位置。所以缺失的位置应该是在未缺失的位置的后面
//如果未出现缺失现象那么就是,最后一个元素缺失,此时应让r++
//还要考虑数组里没有数字,即n=1的时候,范围是0-0
if(nums.empty())return 0;
int l=0,r=nums.size()-1;
while(l<r){
int mid= l+r>>1;
if(nums[mid]!=mid)r=mid;
else l=mid+1;
}if(nums[r]==r)r=r+1;
return r;
}
};