二分查找算法解析
基本框架:
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {int mid = (right + left) / 2;
if (nums[mid] == target) {...} else if (nums[mid] < target) {left = ...} else if (nums[mid] > target) {right = ...}
}
return ...;
}
一、寻找一个数(基本的二分搜索)
这个场景是最简单的,肯能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意
while(left <= right) {int mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
采用 <= 的原因:
因为右边界取的是 nums.length-1,如果是 < 则取不到最后一个边界,在执行 left=mid+ 1 时有可能越界,mid 在 left<right 时是 <mid 的,在 left=right 时是等于 right 的,此时 + 1 越界
while(left < right) 的终止条件是 left == right,在上一次循环中可能修改了其中一个值,导致这个值没有办法访问
上面这种方法是找到一个满足的就终止 return,不一定是第一个
为什么 left = mid + 1,right = mid – 1?我看有的代码是 right = mid 或者 left = mid:
因为 mid 已经搜索过,应该从搜索区间中去除。
此算法有什么缺陷?
无法定位元素第一个或者最后一个。你也许会说,找到一个 target,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的复杂度了。
二、寻找左侧边界的二分搜索
int[] searchRange(int[] nums, int target) {
int i = 0;
int l = 0;int r = nums.length-1;
while(l<=r){
int mid = l + r >>1;
if(nums[mid]>target){r = mid - 1;}else if(nums[mid]<target){l = mid + 1;}else{
i = mid;
r = mid - 1;
}
}
}
也可以写成:
int[] searchRange(int[] nums, int target) {
int i = 0;
int l = 0;int r = nums.length-1;
while(l<=r){
int mid = l + r >>1;
if(nums[mid]>=target){
r = mid - 1;
if(nums[mid]==target){i = mid;}
}else{l = mid + 1;}
}
}
相比找到一个数,这种形式我们不能在找到一个数后停止,还需继续缩小区间到左边,直到找到最左边的那个元素
也就是下面这段,当相等时,不直接 return,而是保存当前值后缩小 right 区间到 mid-1
else if(nums[mid]==target){
i = mid;
r = mid - 1;
}
三、寻找右侧边界的二分查找
int[] searchRange(int[] nums, int target) {
int i = 0;
int l = 0;int r = nums.length-1;
while(l<=r){
int mid = l + r >>1;
if(nums[mid]>target){r = mid - 1;}else if(nums[mid]<target){l = mid + 1;}else{
i = mid;
l = mid + 1;
}
}
}
也可以写成:
int[] searchRange(int[] nums, int target) {
int i = 0;
int l = 0;int r = nums.length-1;
while(l<=r){
int mid = l + r >>1;
if(nums[mid]<=target){
l = mid + 1;
if(nums[mid]==target){i = mid;}
}else{r = mid - 1;}
}
}
相比找到一个数,这种形式我们不能在找到一个数后停止,还需继续缩小区间到右边,直到找到最右边的那个元素
也就是下面这段,当相等时,不直接 return,而是保存当前值后缩小 left 区间到 mid+1
else if(nums[mid]==target){
i = mid;
l = mid + 1;
}
小优化技巧
- mid 计算使用
<<1
代替/2
- 防止溢出:当 l 增大时,l+ r 会很大,可能溢出,可以使用
l+((r-l)>>1)
恰当使用,在不同需求下使用不同的二分查找
参考:https://leetcode-cn.com/probl…
本篇文章由一文多发平台 ArtiPub 自动发布