剑指 Offer 53-I 在排序数组中查找数字C++

-----------------------------------------------------二刷2021/2/4----------------------------------------------------
这几天一天刷十题,很快啊,现在已经是第六天了,前面35题三刷过了,马上后面也要做完了
总的来说该写的之前都写了。
实实在在觉得菜鸡JL有点脱胎换骨了。还有一个月就来到金三了!加油!


二分法太不熟悉了
在这里插入图片描述

解法 二分法

在本题的二分开始之前,先来看看二分的两种方法

  • 第一种 左闭右闭区间 [L , R]

public int binarySearch(int  arr[], int target){
    
    
        if (arr!=null&&arr.length>0){
    
    
            // 注意 l  与 r 的初值
            int l=0,r=arr.length-1;//定义 在[l...r]的范围里寻找target,循环不变量,虽然在下面修改了l与r的值,但是其语义不变,
            //只要还有查找内容的话,就要继续查找
            //当l==r 时,区间[l...r]依然是有效的,也就是还有内容要查找
            //所以是 l<=r 而不是l<r
            while (l<=r){
    
    
                int mid=l+(r-l)/2;//防溢出
                if (arr[mid]==target){
    
    
                    return mid;
                }else if (target>arr[mid]){
    
    
                    l=mid+1;//target在[mid+1...r]中,因为已经明确mid位置及其左边的值都不是目标值
                }else {
    
    
                    r=mid-1;//target在[l...mid-1]中,因为已经明确mid位置及其右边的值都不是目标值
                }
            }
        }
        return -1;
    }
  • 第二种 左闭右开区间[L , R)

public int binarySearch2(int  arr[], int target){
    
    
       if (arr!=null&&arr.length>0){
    
    
          // 注意 l  与 r 的初值
           int l=0,r=arr.length;//定义 在[l...r)的范围里寻找target
           while (l<r){
    
    //当l==r 时,区间[l...r)是无效的,[42,42) ,所以这里是 小于 而不用小于等于
               int mid=l+(r-l)/2;
               if (arr[mid]==target){
    
    
                   return mid;
               }else if (target>arr[mid]){
    
    
                   l=mid+1;//target在[mid+1...r)中,因为mid位置及其左边的值都不是目标值
               }else {
    
    
                   r=mid;//target在[l...mid)中 ,因为已经明确mid位置及其右边的值都不是目标值,这里mid是右开的,所以是取不到的
               }
           }
       }
       return -1;
   }

注释里面都写得很清楚了,推荐是用第一种方法

题目要求我们找出指定数字的出现次数,我们使用二分法查找该数字的左边界和右边界
在这里插入图片描述
答案即是 right - left - 1
普通的二分法只能让我们停留在target的位置,但是鉴于有很多target我们需要修改arr[mid] == target处理方式。
当arr[mid] == target时
1. 若我们要查找右边界,则执行 l = m + 1
循环结束的时候l会停在右边界,r会停留在l-1

2. 若我们要查找左边界,则执行 r = m - 1
循环结束的时候,r会停留左边界,l会停留在 r + 1

这里有个很关键的地方,如果数组中确实存在target时,查找右边界以后,arr[r]就为target。
而同理,查找左边界以后,arr[l]为target。
那么为什么查找右边界的时候 r会停在最右边的target,而查找左边界的时候l会停在最左边的target 是二分最难的地方
分析二分的过程就能知道原因
我们在二分时保证target一定处于区间内,所以这个区间包括了所有的target,直到出现arr[mid] = target。这个时候就必须开始舍弃一部分的target。
1. 若我们要查找右边界,则执行 l = m + 1

  1. 这样左边的target将会一直减少,当l一直移动到r的位置时,mid和r同时处在最后一个target的下一个位置,这时再执行r = m - 1就会使得 l = r + 1,循环终止
  2. 或者 r可能在循环的过程中由于出现过 arr[mid] > target,且mid刚好是右边界,这时候r = m - 1就会停留在最后一个target的位置等待l与其相交 ,相交之后执行 l = m +1使得 l = r + 1,循环终止

2. 若我们要查找左边界,则执行 r = m - 1

  1. 这样右边的target将会一直减少,当r 一直移动到 l的位置,mid 和l同时处在第一个target的上一个位置,这时再执行 l = mid + 1就会使得 l = r + 1.循环结束
  2. 或者同理 l可能在循环的过程中出现过arr[mid] < target ,其mid刚好是左边界,这时候 l = m + 1就会停留在第一个target的位置等待r与其相交。相交之后执行r = m - 1,使得 l = r + 1,循环终止

总之查找右边界的过程中,r一定不会越过最右边的target ,同理查找左边界的过程中,l一定不会越过最左边的target。
通过上诉分析可见其逻辑以及正确性。即 结束时 l == r + 1
具体来分析一个案例
5 7 7 8 8 9 target = 8

  • 查找右边界
    l = 0 r = 5 mid = 2
    arr[mid]=7 < target l =mid + 1
    l = 3 r = 5 mid = 4
    arr[mid]=8 = target l = mid + 1
    l = 5 r = 5 mid = 5
    这时候我们的l已经处于左边界了,但区间内仍有数字所以循环还在继续
    arr[mid]= 9 > target r = mid - 1
    l = 5 r = 4 退出循环
    arr[l] = 9 arr[r] = 8

还有其他情况留给读者自己分析。
代码:

class Solution {
    
    
public:
    int search(vector<int>& nums, int target) {
    
    
        if(nums.size() == 0) return 0;
        //查找右边界
        int l = 0, r = nums.size() - 1;
        while(l <= r) {
    
    
            int mid = (l + r) >> 1;
            //mid小于等于target,即mid在target左边 说明target在[m + 1, j]
            if(nums[mid] <= target) l = mid + 1;
            //mid大于target,即mid在target右边 说明target在[l, m -1]
            else r = mid - 1;
        }
        //这轮结束的时候,l会停在右边界,r会停留在l-1,如果r < 0或者
        //如果nums[r] != target 说明不存在target
        if(r < 0 || nums[r] != target) return 0;
        int right = l;
        //r停留在最后一个target的位置无需改变
        l = 0;
        while(l <= r) {
    
    
            int mid = (l + r) >> 1;
            //mid大于等于target,即mid在target右边 说明target在[l, m -1]
            if(nums[mid] >= target) r = mid - 1;
            //mid小于target,即mid在target左边 说明target在[m + 1,r]
            else l = mid + 1;
        }
        //这轮结束的时候,r会停留左边界,l会停留在 r + 1
        int left = r;
        return right - left - 1;
    }
};

在这里插入图片描述
时间复杂度O(logN)
空间复杂度O(1)

猜你喜欢

转载自blog.csdn.net/qq_42883222/article/details/112350550
今日推荐