个人主页:Ice_Sugar_7
所属专栏:算法详解
欢迎点赞收藏加关注哦!
直接上题:在排序数组中查找元素的第一个和最后一个位置
我们记左、右指针为 left 和 right,中点为 mid,左端点为 target
划分区间:target 左边为一个区间,target 和 target 右边为另一个区间。这样左区间就都小于 target,右区间就都是大于或等于 target
每次循环就是求出中点处的值 x,让 x 和 target 比较
left = mid + 1
right = mid
在上篇文章中,我们讲了两个求中点的公式,分别可求左、右中点(当元素个数为偶数时)
我们以区间内只剩下 left 和 right 这两个元素的情况来分析(在下文分析循环终止条件时,我们可以推出最后一次循环时只剩下两个元素)
如果 mid 为右中点
,并且中点值大于或等于 target,那就会死循环
所以我们要用求左中点的公式
:mid = left + (right - left) / 2
下面解释一下原因:
①如果[left,right]
之间有 target,就是下图的情况:
根据上面对 left 和 right 移动路径的分析,我们可以知道:right 只能在右区间内移动,无法走到左区间;而 left 就不一样了,如果 left 已经走到左区间最后一个位置,那么它最终刚好可以到 target 的位置(因为 left = mid + 1,mid 会和 left 重合,left + 1后就到 target 处)
也就是说,left 和 right 最终相遇的地方就在 target 处,此时没必要进循环了
②如果[left,right]
中的值全都比 target 大,这种情况下 right 就会不断往左走,直到和最左端的 left 相遇;反之,若全比 target 小,则 left 会一直走到 right 处
如果在 left == right 时还进入循环,并且 x >= target,那又会死循环
了(因为 right 一直等于 mid)
所以正确的做法是:不进循环,判断 nums[mid] 是否等于 target,如果不相等那就说明数组中没有这个数
left = mid
right = mid-1
求右中点的公式
:mid = left + (right - left+1) / 2其实什么时候要用哪个中点公式不用死记硬背,分析 mid 分别落在两个区间时 left 和 right 需要如何移动之后,放在最后一次循环中分析,看 mid 在哪个中点的时候可能会死循环,就可以推出要用哪个公式了
这道题代码如下:
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0,right = nums.length - 1;
int[] ret = new int[2];
ret[0] = ret[1] = -1;
if(nums.length == 0) return ret; //排除数组中没有元素的情况
//查找左端点
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] < target) left = mid + 1;
else if(nums[mid] >= target) right = mid;
}
if(nums[left] == target) ret[0] = left;
else return ret;
right = nums.length - 1; //查找右端点前先重置一下 right 的下标,left 可以不用重置
//查找右端点
while(left < right) {
int mid = left + (right - left +1) / 2;
if(nums[mid] <= target) left = mid;
else if(nums[mid] > target) right = mid - 1;
}
ret[1] = left;
return ret;
}
}
由这道题的代码,我们可以总结出以下两个模板:
这两个模板分别是查找某个区间左端点和右端点的,要记忆的话,只需记住 mid 的公式:求左端点时就用左中点公式;反之则用右中点公式
下面的 left 和 right 根据题目分析即可