题解仓库,更新中 GitHub LeetCode-Java
题目大意
给定一个排好序的数组,内里元素可能有重复,给定一个目标值,找出目标值最左端和最右端的index。传送门
典型的二分搜索
二分搜索并不容易写,因为实际上的二分搜索也可以被分成很多类,但是当人们提起时,往往混为一谈,我觉得,基本的,典型的二分搜索有这两类:
- 寻找一个特定值
- 寻找满足某个条件的连续区间的边界
第一种是最经典的二分搜索,也是初学者最有可能接触到的二分搜索。
1 | while (left < right) { |
而第二种则是本题目中的要求,而且本题将上下界都涵盖了,非常经典。
二分搜索的重点
那么,二分搜索的重点有哪些呢?其实只有两点:
- 保证每次循环后,如果我们的解存在,那么一定还在$[left, right]$区间里。
- 每次循环,区间$[left, right]$一定会缩小
我们来看看本题的解法:
1 | private int leftEdge(int[] nums, int target) { |
笔者是先完成了 leftEdge
,后完成 rightEdge
的。从其中的改变可以看出二分搜索三个重点。
改变一
1 | if (nums[mid] >= target) { |
变为
1 | if (nums[mid] <= target) { |
为什么?因为只有这样改变,才能保证重点一的成立。
在 leftEdge
中,right = mid
而不是 right = mid - 1
是因为如果 mid
已经是所求解,即区间左端点,那么 mid - 1
会将解排除在外。
同理,rightEdge
中,left = mid
而不是 left = mid + 1
。
改变二
1 | int mid = (right - left) / 2 + left; |
变为
1 | int mid = (right - left) / 2 + left + 1; |
这一改变其实是与改变一联动,其目的是保证重点二的成立。为什么?
因为如果在 rightEdge
中还使用经典的 int mid = (right - left) / 2 + left;
,由于我们使用了 left = mid
来缩减区间,试想如果 right = 2, left = 1
且 nums[mid] <= target
成立, 那么经过计算 mid
永远等于 left
,也即区间其实不会缩小,程序进入死循环。
由于两个方法都是为了寻找区间端点,终归相似之处较多,也基本属于经典范畴。
但是 LeetCode 上还有另外一题 0033 Search-in-Rotated-Sorted-Array 是一道较为新颖的二分搜索题,乍一看由于没有了经典二分中的序关系,不像二分,但想懂后还是很舒畅的。