二分

该面对的总是要面对
LeetCode34. 在排序数组中查找元素的第一个和最后一个位置
题意是,在某个排序数组里找到target的第一次出现和最后一次出现。

有两种解法

第一种 [l,r]

        while(l<=r)
        {
            int mid = (l+r)/2;
            if(nums[mid]<target) l = mid+1;
            else r = mid-1;
        }
        a[0] = l;
        if((a[0]==nums.size())||nums[a[0]]!=target) a[0] = -1;

由于 [l,r]

  1. 合法区间
    把问题转换为对合法区间的切分,在寻找左端点的情况下,相当于<target为合法,>=target为不合法。当mid值小于target时,说明此时l到mid都是合法的(包括mid)因此将l右移缩小范围。当mid大于等于target时,说明l到mid不合法,可以舍弃,使r = mid-1(即一个有可能的合法值)。

  2. 边界问题
    二分到最后l=r,(l+r)/2=l=mid,此时

    • 若合法:l=mid+1,结果就是l成为了不合法的那一个(因为mid也就是r合法)
    • 若不合法:r=mid-1,l不变,l依旧不合法。

    此时l=r+1。找到的最终合法值是r,在此题中就是第一个target的左边那一个,所以理论上l应该是第一个target。

  3. 若查找失败
    如果停在了数组内部,那么找到的r依旧是比target小一点的那一个,但nums[l]!=target。
    如果全部数字都大于target,依旧可以用nums[l]!=target。
    如果全部数字小于target此时l应该越界了,判断是否等于n即可。

对右边端点的查找同理,合法区间变为<=target和>target,最终l非法r合法。找到的最大一个target是r,l是比target大一点点那个。

        l = 0, r = nums.size()-1;
        while(l<=r)
        {
            int mid = (l+r)/2;
            if(nums[mid]<=target) l = mid+1;
            else r = mid-1;
        }
        a[1] = r;
        if(l==0||nums[a[1]]!=target) a[1] = -1;

查找失败的情况有所不同,因为最终判断合法是通过判断nums[pos]?=target,所以在这里应该用r来判断,r超出数组范围的情况就是r=-1,l=0。

第二种 [l,r)

在区间选择上,天生l合法r不合法,因此当l=r(冲突)时退出循环。

        int l = 0, r = n;
        while (l < r) {
            int mid = (l + r) / 2;
            if (nums[mid] < target) l = mid + 1;
            else r = mid;
        }
        if (r == n || nums[r] != target) return {-1, -1};
  1. 合法区间
    在转移的时候r=mid,而不是mid-1,因为此时mid已经验证不合法,然后r刚好是一个不合法值。感觉就是l代表合法阵营,r代表不合法阵营,在搜索他们那个分界点,更直观?

  2. 边界问题
    二分到最后l=r-1,(l+r)/2=l=mid,此时

    • 若l合法:l+1,最终l不合法了
    • 若l不合法:r=l,l不变,l依旧不合法。

    此时l=r=第一个target。

  3. 若查找失败
    如果停在了数组内部,nums[l]!=target。
    如果全部数字都大于target,依旧可以用nums[l]!=target。
    如果全部数字小于target此时l应该越界了,判断是否等于n即可。

实际上可以假装nums[-1]的坐标有一个-inf,nums[n]有一个inf,就不用判断边界辣

posted @ 2020-12-01 21:19  nanf0621  阅读(17)  评论(0编辑  收藏  举报