用一个例子体会二分查找基本思想,理解开区间和闭区间的区别,以及>=,>,<=,<这四种情况下如何解决,写出二分基本模板
问题
给定一个有序数组和元素x,返回其中第一个大于等于元素x的下标,如果都小于x,则返回其数组长度,例如数组num = [5,7,7,8,8,10],返回第一个>=8的下标。
一、解决模板
1.闭区间
l和r分别指向左右边界,设l = 0,r = num.size() - 1,mid =(l + r)/ 2向下取整,为防止溢出,可以令mid = l + (r -l)/ 2。
当mid < 8 时,需要令l=mid+1,为什么不是等于mid呢,因为mid<8时,代表从l到mid这段上的元素都是小于8的,这是闭区间,所以我们需要从mid+1开始,如果l=mid,那么此时就变成左开右闭区间了,因此下一步就要要将l = mid+1,这也同时说明,l-1指向的一定小于8;当mid>=8时,代表mid到r都是大于等于8的,剩下不确定的区间为[l,mid-1],所以将r = mid - 1,同时说明,r+1一定大于等于8。
当循环结束后,r+1始终大于等于8,所以r+1就是我们要寻找的答案,由于循环结束r+1 = l,所以答案也可以用l来表示。
代码实现就如下:
int left = 0;
int right = num.size() - 1;//闭区间[left,right]
while(left <= right){//区间不为空
int mid = left + (right - left) / 2;
if(num[mid] < 8){
left = mid + 1;//[mid+1,right]
}
else{//[left,mid-1]
right = mid - 1;
}
}
return left;
2.左闭右开区间
l指向左边界,r指向右边界加1,设l = 0,r = num.size(),mid =(l + r)/ 2向下取整,为防止溢出,可以令mid = l + (r -l)/ 2。
当mid < 8 时,代表l到mid都是小于8,左边还是闭区间,因此下一步就要要将l = mid+1,这也同时说明,l-1指向的一定小于8;当mid>=8时,代表mid到r都是大于等于8的,剩下不确定的区间为[l,mid),所以将r = mid ,同时说明,r一定大于等于8。
最后当l等于r时,代表区间为空了,循环结束,此时l和r都是指向答案的
代码如下:
int left = 0;
int right = num.size();//左闭右开区间[left,right)
while(left < right){//区间不为空
int mid = left + (right - left) / 2;
if(num[mid] < 8){
left = mid + 1;//[mid+1,right)
}
else{//[left,mid)
right = mid;
}
}
return left;
3.开区间
l指向左边界-1,r指向右边界加1,设l = -1,r = num.size(),mid =(l + r)/ 2向下取整,为防止溢出,可以令mid = l + (r -l)/ 2。
当mid < 8 时,代表l到mid都是小于8,左边还是开区间,因此下一步就要要将l = mid,这也同时说明,l指向的一定小于8;当mid>=8时,代表mid到r都是大于等于8的,剩下不确定的区间为[l,mid),所以将r = mid ,同时说明,r一定大于等于8。
最后当l+1等于r时,代表区间为空了,循环结束,此时l+1和r都是指向答案的
代码如下:
int left = -1;
int right = num.size();//左闭右开区间(left,right)
while(left+1 < right){//区间不为空
int mid = left + (right - left) / 2;
if(num[mid] < 8){
left = mid;//(mid,right)
}
else{//(left,mid)
right = mid;
}
}
return right;
二、>= > < <=四种解决方式
如果题目要求的不是返回大于等于而是其他的呢,那么我们又该如果解决
我们需要知道,不管是大于,还是小于,还是小于等于,这四种类型都是可以互相转换来的,我们都可以通过大于等于来转换过来。
对于大于来说,设大于的target为x,我们可以把他看成是大于等于x+1,对于整数来说;对于小于来说,可以看成大于等于x的那个数的左边的那个数;对于小于等于,我们可以看成大于x的那个数左边的那个数