排除法二分查找
基础二分查找
以找到目标元素为目的进行查找,循环控制条件为while(left<=right),表示当left==right成立时,还要判断left(right)值和目标值的关系。
基础二分法将查找数组分成三部分:mid所在位置,mid左边位置和mid右边位置,如果nums[mid]==target,那么直接返回mid,否则根据条件改变搜索区间进行下一次搜索。
存在的问题:
目标值在数组中存在多个,但需要返回的是一个边界值,例如比小于等于target的第一个值等。
排除法二分查找
从哪些元素不是目标元素考虑。思路是:根据mid位置的元素,排除不可能存在目标元素的空间,下一轮在可能存在目标元素的区间中查找。
具体算法:
1.循环条件while(left<right)
循环过程中,left不断右移,right不断左移。退出循环的时候一定有left==right成立。注意:此时left(right)处的值可能还没有被读取,因此可能需要对left(right)处的值是否是目标元素的值做一次判断。
2.写if和else语句时考虑nums[mid]为什么值时mid不是解,进而判断mid的左侧是否存在解,mid的右侧是否存在解。
两种边界收缩行为
1.mid被分到左边,即区间被分成[left, mid]和[mid+1, right]
if(check(mid)){
// 下一轮检查的是[left, mid]
right = mid;
}else{
// 否则检查[mid+1, right]
left = mid + 1;
}
2.mid被分到右边,即取键被分成[left, mid-1],[mid, right]
if(check(mid){
// 下一轮检查的是[left, mid-1]
right = mid - 1;
}else{
// 否则检查[mid, right]
left = mid;
}
根据边界收缩行为修改中间数的行为
中间数的一般取法
int mid = (left + right) / 2;
如果left和right特别大可能发生整形溢出,改进的取法为:
int mid = left + (right - left) / 2;
这种写法几乎不会发生溢出。
注意/运算符是向下取整的,因此int mid = left + (right - left) / 2这种写法永远不会取到区间的最右侧元素。
在第二种边界收缩行为中,待搜索区间只有两个值时可能会发生死循环。

因此取中间数时根据两种边界收缩行为分成两种情况
1.mid分给左侧区间,[left, mid]和[mid+1, right],此时mid向下取整
int mid = left + (right - left) / 2;
if(check(mid){
right = mid;
}else{
left = mid + 1;
}
2.mid分给右侧区间,[left,mid-1]和[mid, right],此时mid向上取整
int mid = left + (right - left) / 2;
if(check(mid){
right = mid - 1;
}else{
left = mid;
}
一种避免整形溢出的取中间数方法
int mid = (left + right) >>> 1;
>>>为无符号右移,使用这种方法可以避免整形溢出
排除二分法的两种模板
1.mid分给左侧区间[left, mid],[mid+1, right]
while(left < right){
int mid = (left + (right - left) / 2) >>> 1;
if(check(mid)){
right = mid;
}else{
left = mid + 1;
}
}
2.mid分给右侧区间[left, mid - 1],[mid, right]
while(left < right){
int mid = (left + (right - left + 1) / 2) >>> 1;
if(check(mid)){
right = mid - 1;
}else{
left = mid;
}
}
参考leetcode算法35题liweiwei1419大佬的解题,原文在这。
本文深入解析二分查找算法,包括基础二分查找和排除法二分查找,探讨了目标元素定位及边界值寻找的策略。文章详细介绍了二分查找的循环控制、中间数选取和边界收缩行为,以及如何避免整形溢出,提供了两种实用的二分查找模板。
4253





