34. 在排序数组中查找元素的第一个和最后一个位置
二分查找的理解要加强。
对于数组1 2 3 4 5 5 5 5 6 7 8
两种思路都是致力于把循环区间缩减至0。
注意二分查找的查找区间是左闭右开的。
邓书的版本理解为找第一个大于target的数:
- 当target>=nums[mid],那么狠狠把下一次搜索区间往前移,因为显然nums[mid]及其之前的元素都不是第一个大于taeget的元素;lo=mid+1
- 当target<nums[mid],让hi=mid,把mid排除在下一次搜索范围之外,可能nums[mid]已经是最终答案,但还要这样做,是采取了大胆的策略,相信最终答案在前面。hi=mid
区间缩减至0时,lo==hi就是所求的位置
还有一种二分查找是找第一个等于target的数:
- 当target>nums[mid]时,说明nums[mid]及其之前的元素都不是第一个等于target的元素。lo=mid+1
- 当target<=nums[mid],大胆策略,我们还相信前面才是最终答案,把mid排除咋搜索范围之外,hi=mid。
两种思路可以用代码中的注释来理解,代码前的注释别看了。
二分查找有几种写法?它们的区别是什么?
这篇回答讲的好。
class Solution {
//两个二分查找?
//邓书版二分查找:左闭右开
//中位数取得是lo+(hi-lo)/2,应该是靠右的中位数
//边界的确定,要求右边([hi,n))是>目标值的区间(左闭右开的),中间是待搜索区间(左闭右开的),左边([0,lo)是不大于目标值的区间(左闭右开的)
//最后当待搜索区间为0时跳出循环
//关于中间值的取法貌似也是易错点:看最后两个数和一个数的情况和退出循环的条件
/*
1.划分 [left, mid] 与 [mid + 1, right] ,mid 被分到左边,对应mid = left + (right - left) / 2;;
2.划分 [left, mid - 1] 与 [mid, right] ,mid 被分到右边,对应mid = left + (right - left + 1) / 2;。
看1,关键是只剩两个元素的时候,若取中间数的取法是取靠右的区间,mid又被划到左区间,左区间将永远是两个元素,无法将区间缩小:[left,left+1],下一轮的mid=left+1,而[left,mid]被划分为左区间,有区间为空,下一轮呢,计算mid还是等于left+1.无论如何都不会将区间减小至1或者是0。
记忆:看看mid是被划到左区间还是右区间,如果mid被划到左区间,中间数应该靠左,被划到右区间,中间数应该靠右。
当然,如果mid单独作为一个判断,不合并到左边或者右边的区间,应该是没有这个烦恼的。但在一些问题中,mid合并到区间中可以方便的解决问题。
----
以上思路全都不合适。。还是按注释来吧
*/
public int[] searchRange(int[] nums, int target) {
int lo=0,hi=nums.length;
int mid=0;
int[] res=new int[]{-1,-1};
if(nums.length==0) return res;
//找第一个大于target的秩(对应的元素称为目标值)
while(lo<hi){
mid=lo+(hi-lo)/2;//因为hi的定义,中间数是靠右的
if(target<nums[mid])//当前数组元素大于target,那么目标值在前面(有可能已经是了),大胆nums[mid]排除在下一次搜索范围之外
hi=mid;//
else//当前数组元素不大于target,那么目标值一定在后面,大胆把mid排除在外(也即排除在下一次搜索区间之外)
lo=mid+1;
}
if(lo-1>=0&&nums[lo-1]==target)
res[1]=lo-1;
lo=0;
hi=nums.length;
//找第一个等于target的秩(对应的称为目标值)
while(lo<hi){
mid=lo+(hi-lo)/2;//因为hi的定义,中间数是靠右的
if(target>nums[mid])//如果当前元素小于target,目标值在后面,那么把当前的nums[mid]排除在搜索范围之外
lo=mid+1;
else//如果当前元素大于等于target,目标值在前面(可能nums[mid]已经是目标值),也大胆的把当前元素排除在下一次搜索范围之外
hi=mid;
}
if(lo>=0&&lo<=nums.length-1&&nums[lo]==target)
res[0]=lo;
return res;
}
}

浙公网安备 33010602011771号