文章目录
二分查找的细节
1、 二分查找的使用场景
场景 一 (最经典的二分查找使用场景):给定一个升序数组int[] nums
,查找是否存在目标值target
,如果存在返回该值/返回true
/返回该值的下标值,不存在返回 -1 / 返回 false
。
场景 二:现在我们要查找的目标值不止一个,返回该目标值的左边界/右边界/[左边界,右边界]
最开始我们学习的方法是什么呢?
答:我们可以使用最基础的二分查找找到目标值target
,然后定义一个“指针index”向这个target
的前后继续进行比较,如果nums[index] = target
就将index
加入到结果中(如果需要找到所有target
的下标),但是这样难以保证二分查找对数级的复杂度
2、关注的细节问题
二分查找的思路很简单,但是有很多细节问题需要关注
- 定义左右指针
left
,right
的取值,我们经常可以看到right = nums.length - 1 / right = nums.length
- while 中的条件 是
left <= right
还是left < right
涅 - 进行再次二分时,
left
和right
到底是 加 1 还是 减1 涅?偶尔也是right = mid
,偶尔是right = mid - 1
- 不清楚细节造成数组的索引越界
3、寻找一个 target(最基本的二分查找)
先给出二分查找的框架,其中 “…” 号的部分就是需要每次根据实际题目注意调整和容易出错的细节
int binarySearch(int[] nums,int target){
int left = 0, right = ...;
while(...){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
...
} else if(nums[mid] < target){
left = ...
} else if(nums[mid] > target){
right = ...
}
}
return ...;
}
}
使用一个技巧:不要在条件判断中出现 else ,将所有的情况用 else if 写清楚,就可以清楚地展现所有地细节
计算 mid 时需要防止溢出,代码中left + (right - left) / 2
就和(left + right) / 2
的结果相同,但是有效防止了 left 和 right 太大直接相加导致溢出
这是基本二分查找的代码:
int binarySearch(int[] nums,int target){
int left = 0, right = nums.length-1;//注意
while(left <= right){
int mid = left + (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;
}
下面我们分析 right 的取值 和 while 里面的终止条件是怎么回事?
right = nums.length - 1
这是最后一个元素的索引,我们需要判断这个元素是否是题目要查找的target
,所以我们的查找区间应该是[left,right]
,即一个闭区间,那么我们的退出条件就应该是left > right
,即left = nums.length
此时索引越界right = nums.length
这不是我们最后一个元素的索引不应该包含在查找的区间里面,所以我们的查找区间是[ left, right)
,那么 while 中的退出条件就应该是left = right
,此时仍然是left = nums.length
索引越界的情况
小总结:我们必须要清楚进行搜索的区间是什么,然后去判断 while 循环应该终止的条件,同时应该考虑索引越界的情况,考虑是否每一个元素我们都进行了判断;这些会在后面的代码中体现
什么时候我们就不用再继续查找了呢?答案显而易见就是当我们找到目标值target
的时候就不用继续查找了
if(nums[mid] == target) return mid;
如果没找到,就是 while 循环终止,返回 -1;
-
while(left <= right)
的终止条件是left == right + 1
,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可 -
while(left < right)
的终止条件是left == right
,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间[2, 2]被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的
如果我们一定要用 while(left < right)
,那么我们就增加一个判断,保证最后一个数不漏掉
while(left < right){
...
}
return nums[left] == target ? left : -1;
- 为什么
left = mid + 1
,right = mid - 1
?有的代码是right = mid
或者left = mid
,没有这些加加减减,到底怎么回事,怎么判断?
我们一定要明确自己的搜索区间,我们的搜索区间是 [left,right],当发现mid
不是要找的target
时,下一步应该搜索哪里涅? 当然是去搜索 [left, mid-1]或者