34. 在排序数组中查找元素的第一个和最后一个位置

二分查找的理解要加强。
对于数组1 2 3 4 5 5 5 5 6 7 8
两种思路都是致力于把循环区间缩减至0。
注意二分查找的查找区间是左闭右开的。
邓书的版本理解为找第一个大于target的数:

  1. 当target>=nums[mid],那么狠狠把下一次搜索区间往前移,因为显然nums[mid]及其之前的元素都不是第一个大于taeget的元素;lo=mid+1
  2. 当target<nums[mid],让hi=mid,把mid排除在下一次搜索范围之外,可能nums[mid]已经是最终答案,但还要这样做,是采取了大胆的策略,相信最终答案在前面。hi=mid
    区间缩减至0时,lo==hi就是所求的位置

还有一种二分查找是找第一个等于target的数:

  1. 当target>nums[mid]时,说明nums[mid]及其之前的元素都不是第一个等于target的元素。lo=mid+1
  2. 当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;
    }

}
posted @ 2021-03-30 19:42  wsshub  阅读(117)  评论(0)    收藏  举报