排除法二分查找
基础二分查找
以找到目标元素为目的进行查找,循环控制条件为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大佬的解题,原文在这。