二分查找
适合场景:
- 寻找一个数
- 寻找左侧边界
- 寻找右侧边界
最大的一个前提是:需要在单调区间内
1. 框架
1 | int binarySearch(int[] nums, int target){ |
2. 寻找一个数
1 | int binarySearch(int[] nums, int target) { |
while(left <= right) 这里是 <= 是因为要把区间里所有的数据都判断一遍,如果是 while(left < right) 那么 left = right 的这个下标对应的数据就没有进行判断,可以修改return来达到最终目标
while(left < right){...} return nums[left] == target ? left : -1;
- 此算法有什么缺陷?
答:至此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。
比如说给你有序数组 nums = [1,2,2,2,3],target = 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的。
这样的需求很常见。你也许会说,找到一个 target 索引,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的复杂度了。
3. 寻找左侧边界
1 | ① |
int right = nums.length
表示 [left, right) 范围内的数据都要考虑,而 right 对应的数据不做考虑,所以当 nums[mid] > target
时, right = mid
即可。而当 nums[mid] == target
时,因为目标是拿到做边界,所以需要让 right = mid
来压缩区间范围,向左靠拢
更推荐第一种,从左侧边界这个点考虑,需要求出来的是一个index,小于这个index的所有数据都是小于target的,而不去关心右侧边界具体是什么,那么搜索区间保证是 [left, right) 即可
4. 寻找右侧边界
1 | ① |
nums[mid] == target
时压缩右侧区间,所以让 left = mid + 1
。最后返回 left - 1
和 right - 1
是一样的,因为 while 退出的时候,left 和 right 相等,-1 可以这样想,如果 nums[mid] == target
时,left = mid + 1
也就是 mid = left - 1
,最后返回的时候,left 对应的值不是等于 target 的下标,而 -1 后的 mid 有可能符合结果。
5. 总结
在寻找左侧或右侧边界的时候,设想 nums=[1, 2, 2, 2, 3]
,需要target=2的左侧和右侧,保证 [left, right) 左闭右开的搜索区间