【代码随想录刷题记录】LeetCode34在排序数组中查找元素的第一个和最后一个位置

题目地址

最近忙活实验,实在没空刷题,这个题对我来说难度还蛮大的,尤其是理解那个找左边界和找右边界的条件,后来我按照自己的理解写了出来(感觉给的答案解释起来有点反认识规律),所以我从0开始推导一下这个题的解法,算法解析写的不详细的文章我实在看不懂,没时间去再看一遍稿子里有没有打错字的地方,如果有错误,欢迎评论区指出(大概没空回来改)

1.思路

1.1 三种基本情况

我们注意到,这个问题无非三种情况,就如卡尔老师说的(直接引用一下哈哈哈,偷懒):

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4,
    5},target为6,此时应该返回{-1, -1}
  • 情况二:target在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
  • 情况三:target在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
    这三种情况都考虑到,说明就想的很清楚了。

1.2 基本思路分析

题目要求是 O ( l o g ( n ) ) O(log(n)) O(log(n))的复杂度,肯定要魔改二分查找,先来回顾一下二分查找(以左闭右闭为例),假设有如下这样一个升序(可以包括多个重复的值)数组nums,其target值为3:

第1步 : middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 0 2 ⌋ + 0 = 4 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-0}{2}\right\rfloor+0=4 middle =⌊2 right − left ⌋+ left =⌊29−0⌋+0=4
target = 3 = nums middle = nums 4 = 3 \text{target}=3=\text{nums}\\text{middle}=\text{nums}4=3 target=3=numsmiddle=nums4=3

此时就直接找到了3,但是我们要找的是多个连续重复的值的开头的下标和结尾的下标,也就是找到第一个3和最后一个3的下标(即{2,6}),我们就得想办法找到它的边界,也就是我们要想方设法让middle继续向左移动找到左边界,然后还要让middle向右移动找到右边界,回顾一下二分查找(左闭右闭)的代码:

cpp 复制代码
class Solution {
public:
    //左闭右闭
    int search(vector<int>& nums, int target) {
        int left = 0; //最左侧下标
        int right = nums.size() - 1; //最右侧下标
        /*注意!!!求解中点是不断地求解,不是求解一次就完成了,
        我一开始把求解中点写到循环外了,这是错误的*/
        //(错误)int middle = ((right - left) >> 1) + left; //求解中点下标
        // 两边都能取到,所以用小于等于
        while(left <= right)
        {
            // 中点的判断条件是right - left(区间长度)除2(相当于右移1位)再加left(因为区间不一定是从0开始的)
            int middle = ((right - left) >> 1) + left; //求解中点下标
            //如果中间值就是target,直接返回中间下标
            if(target == nums[middle])
            {
                return middle;
            }
            //如果target大于中间值,向右子列寻找,则left变成middle+1
            else if(target > nums[middle])
            {
                left = middle + 1;
            }
            //如果target小于中间值,向左子列寻找,则right变为middle-1(因为是左闭右闭,闭合包括区间端点,所以需要抠除middle)
            else
            {
                right = middle - 1;
            }
        }
        //都过一遍没找到,返回-1
        return -1;
    }
};

这段代码的核心功能就是这个循环:

cpp 复制代码
        while(left <= right)
        {
            // 中点的判断条件是right - left(区间长度)除2(相当于右移1位)再加left(因为区间不一定是从0开始的)
            int middle = ((right - left) >> 1) + left; //求解中点下标
            //如果中间值就是target,直接返回中间下标
            if(target == nums[middle])
            {
                return middle;
            }
            //如果target大于中间值,向右子列寻找,则left变成middle+1
            else if(target > nums[middle])
            {
                left = middle + 1;
            }
            //如果target小于中间值,向左子列寻找,则right变为middle-1(因为是左闭右闭,闭合包括区间端点,所以需要抠除middle)
            else
            {
                right = middle - 1;
            }
        }

1.3 探究找右边界

如果我想让middle继续向左右两侧移动,那target == numsmiddle这个条件必须融到target > numsmiddle或者target < numsmiddle这两个条件中(取并集),原因是当target == numsmiddle时,它只能找到连续重复序列的中间位置的元素,就退出循环了,为了不让它退出循环继续让middle移动,所以我们要重新审视一下if else的条件,如果target == numsmiddle与target > numsmiddle做并集得到target >= numsmiddle这样的条件,那么重新来一遍这个查找的过程:

cpp 复制代码
        while(left <= right)
        {
            int middle = ((right - left) >> 1) + left; //求解中点下标
			if(target >= nums[middle])
            {
                left = middle + 1;
            }
            else
            {
                right = middle - 1;
            }
        }

1.3.1 target在数组范围中,且数组中存在target

我开头举的例子恰好是"target在数组范围中,且数组中存在target"的情况,下面开始迭代:
第1步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 0 2 ⌋ + 0 = 4 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-0}{2}\right\rfloor+0=4 middle =⌊2 right − left ⌋+ left =⌊29−0⌋+0=4

target = 3 = nums middle = nums 4 = 3 \text{target}=3=\text{nums}\\text{middle}=\text{nums}4=3 target=3=numsmiddle=nums4=3,故 left = middle + 1 = 4 + 1 = 5 < right = 9 \text{left}=\text{middle}+1=4+1=5<\text{right}=9 left=middle+1=4+1=5<right=9,满足循环条件,继续循环

第2步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 5 2 ⌋ + 5 = 7 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-5}{2}\right\rfloor+5=7 middle =⌊2 right − left ⌋+ left =⌊29−5⌋+5=7

target = 3 < nums middle = nums 7 = 4 \text{target}=3<\text{nums}\\text{middle}=\text{nums}7=4 target=3<numsmiddle=nums7=4,故 right = middle − 1 = 7 − 1 = 6 > left = 5 \text{right}=\text{middle}-1=7-1=6>\text{left}=5 right=middle−1=7−1=6>left=5,满足循环条件,继续循环

第3步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 6 − 5 2 ⌋ + 5 = 5 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{6-5}{2}\right\rfloor+5=5 middle =⌊2 right − left ⌋+ left =⌊26−5⌋+5=5

target = 3 = nums middle = nums 5 = 3 \text{target}=3=\text{nums}\\text{middle}=\text{nums}5=3 target=3=numsmiddle=nums5=3,故 left = middle + 1 = 5 + 1 = 6 = right = 6 \text{left}=\text{middle}+1=5+1=6=\text{right}=6 left=middle+1=5+1=6=right=6,满足循环条件,继续循环

到这里,我们发现,找到了右边界,但是循环还没停,继续下一步:
第4步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 6 − 6 2 ⌋ + 6 = 6 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{6-6}{2}\right\rfloor+6=6 middle =⌊2 right − left ⌋+ left =⌊26−6⌋+6=6

target = 3 = nums middle = nums 6 = 3 \text{target}=3=\text{nums}\\text{middle}=\text{nums}6=3 target=3=numsmiddle=nums6=3,故 left = middle + 1 = 6 + 1 = 7 > right = 6 \text{left}=\text{middle}+1=6+1=7>\text{right}=6 left=middle+1=6+1=7>right=6,不满足循环条件,退出循环,最后right,middle和left三个值对应的下标是这样的:

我们看到,右边界的下标可能是right或者middle或者是left-1,但是注意,结束循环后直接在循环外返回right,left或者提前定义middle,然后循环外返回middle可能都有问题,原因是我们最后还有找不到返回特定值(如-1或者-2,只要不是0开始的自然数,也就是下标的合法值,其余情况都行),这个题没让我们返回没找到的元素应插入的位置(相关文章),所以不能直接返回right和middle,只能在循环外再定义一个变量,因为我们探究到这种情况是找到右边界,所以我们将变量声明为int rightBorder = -1(一开始就定义好数组为空的情况),然后我们要想办法让rightBorder这个变量跟随着循环迭代更新,但是究竟要记录right还是left-1还是middle?这里就要考虑另外两种特殊情况,假设找不到target

1.3.2 target在数组范围中,且数组中不存在target

有如下如下这样一个升序(可以包括多个重复的值)数组nums,其target值为5:

第1步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 0 2 ⌋ + 0 = 4 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-0}{2}\right\rfloor+0=4 middle =⌊2 right − left ⌋+ left =⌊29−0⌋+0=4

target = 5 > nums middle = nums 4 = 4 \text{target}=5>\text{nums}\\text{middle}=\text{nums}4=4 target=5>numsmiddle=nums4=4,故 left = middle + 1 = 4 + 1 = 5 < right = 9 \text{left}=\text{middle}+1=4+1=5<\text{right}=9 left=middle+1=4+1=5<right=9,满足循环条件,继续循环

第2步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 5 2 ⌋ + 5 = 7 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-5}{2}\right\rfloor+5=7 middle =⌊2 right − left ⌋+ left =⌊29−5⌋+5=7

target = 5 < nums middle = nums 7 = 7 \text{target}=5<\text{nums}\\text{middle}=\text{nums}7=7 target=5<numsmiddle=nums7=7,故 right = middle − 1 = 7 − 1 = 6 > left = 5 \text{right}=\text{middle}-1=7-1=6>\text{left}=5 right=middle−1=7−1=6>left=5,满足循环条件,继续循环

第3步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 6 − 5 2 ⌋ + 5 = 5 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{6-5}{2}\right\rfloor+5=5 middle =⌊2 right − left ⌋+ left =⌊26−5⌋+5=5

target = 5 < nums middle = nums 5 = 6 \text{target}=5<\text{nums}\\text{middle}=\text{nums}5=6 target=5<numsmiddle=nums5=6,故 right = middle − 1 = 6 − 1 = 5 = left = 5 \text{right}=\text{middle}-1=6-1=5=\text{left}=5 right=middle−1=6−1=5=left=5,满足循环条件,继续循环

第4步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 5 − 5 2 ⌋ + 5 = 5 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{5-5}{2}\right\rfloor+5=5 middle =⌊2 right − left ⌋+ left =⌊25−5⌋+5=5

target = 5 < nums middle = nums 5 = 6 \text{target}=5<\text{nums}\\text{middle}=\text{nums}5=6 target=5<numsmiddle=nums5=6,故 right = middle − 1 = 5 − 1 = 4 < left = 5 \text{right}=\text{middle}-1=5-1=4<\text{left}=5 right=middle−1=5−1=4<left=5,不满足循环条件,跳出循环

再加上middle的情况:

我们发现,找不到的情况下,middle并不能像二分查找那样返回-1,此时并不能直接将rightBorder设置为right,left-1和middle,看起来在找不到元素的情况下,我们还是没法直接让循环外的rightBorder变量设置为right,left-1,middle三者中的任意一个值,可能最终判断条件和找左侧边界有关,接下来看第三种情况:

1.3.3 target 在数组范围的右边或者左边

假设有如下数组nums,target为1:

第1步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 3 − 0 2 ⌋ + 0 = 1 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{3-0}{2}\right\rfloor+0=1 middle =⌊2 right − left ⌋+ left =⌊23−0⌋+0=1

target = 1 < nums middle = nums 1 = 3 \text{target}=1<\text{nums}\\text{middle}=\text{nums}1=3 target=1<numsmiddle=nums1=3,故 right = middle − 1 = 3 − 1 = 2 > left = 0 \text{right}=\text{middle}-1=3-1=2>\text{left}=0 right=middle−1=3−1=2>left=0,满足循环条件,继续循环

第2步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 2 − 0 2 ⌋ + 0 = 1 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{2-0}{2}\right\rfloor+0=1 middle =⌊2 right − left ⌋+ left =⌊22−0⌋+0=1

target = 1 < nums middle = nums 1 = 3 \text{target}=1<\text{nums}\\text{middle}=\text{nums}1=3 target=1<numsmiddle=nums1=3,故 right = middle − 1 = 2 − 1 = 1 > left = 0 \text{right}=\text{middle}-1=2-1=1>\text{left}=0 right=middle−1=2−1=1>left=0,满足循环条件,继续循环

第3步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 1 − 0 2 ⌋ + 0 = 0 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{1-0}{2}\right\rfloor+0=0 middle =⌊2 right − left ⌋+ left =⌊21−0⌋+0=0

target = 1 < nums middle = nums 0 = 2 \text{target}=1<\text{nums}\\text{middle}=\text{nums}0=2 target=1<numsmiddle=nums0=2,故 right = middle − 1 = 1 − 1 = 0 = left = 0 \text{right}=\text{middle}-1=1-1=0=\text{left}=0 right=middle−1=1−1=0=left=0,满足循环条件,继续循环

第4步

middle = ⌊ right − left 2 ⌋ + left = ⌊ 0 − 0 2 ⌋ + 0 = 0 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{0-0}{2}\right\rfloor+0=0 middle =⌊2 right − left ⌋+ left =⌊20−0⌋+0=0


target = 1 < nums middle = nums 0 = 2 \text{target}=1<\text{nums}\\text{middle}=\text{nums}0=2 target=1<numsmiddle=nums0=2,故 right = middle − 1 = 0 − 1 = − 1 < left = 0 \text{right}=\text{middle}-1=0-1=-1<\text{left}=0 right=middle−1=0−1=−1<left=0,不满足循环条件,跳出循环

此时的right为-1,再把middle的情况加上去:

1.3.4 找右边界总结

在1.3.1中,我们看到middle应该是和right一样的,在这里,middle是在right前面,所以rightBorder不应该被设置为middle或者middle-1,应该设置为right或者left-1,但是在1.3.2"target在数组范围中,且数组中不存在target"情况中,rightBorder本应该设置为找不到的值(-1之类),但是如果在这种情况下将rightBorder设置为right或者left-1,似乎不合理,但是我们先忘掉这种"不合理",按照现在整理出来的思路,将找右边界的函数写出来,即:

cpp 复制代码
    //找右边界
    int getRightBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int rightBorder = -1; 
        //左闭右闭
        while (left <= right)
        {
            int middle = left + ((right - left) >> 1); //除2相当于右移1位,节省时间
            if (target >= nums[middle])
            {
                left = middle + 1;
            }
            else
            {
                right = middle - 1;
            }
            rightBorder = right; //写rightBorder = left - 1也行
        }
        return rightBorder;
    }

1.4 探究找左边界

左边界与右边界在条件上处理是类似的,如果我想让middle继续向左侧移动,那target == numsmiddle这个条件改为target <= numsmiddle这样的条件,即循环框架大概是这样的:

cpp 复制代码
        while(left <= right)
        {
            int middle = ((right - left) >> 1) + left; //求解中点下标
			if(target <= nums[middle])
            {
                right = middle - 1;
            }
            else
            {
                left = middle + 1;
            };
        }

1.4.1 target在数组范围中,且数组中存在target

假设有如下这样一个升序(可以包括多个重复的值)数组nums,其target值为3:

第1步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 0 2 ⌋ + 0 = 4 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-0}{2}\right\rfloor+0=4 middle =⌊2 right − left ⌋+ left =⌊29−0⌋+0=4

target = 3 = nums middle = nums 4 = 3 \text{target}=3=\text{nums}\\text{middle}=\text{nums}4=3 target=3=numsmiddle=nums4=3,故 right = middle − 1 = 4 − 1 = 3 > left = 0 \text{right}=\text{middle}-1=4-1=3>\text{left}=0 right=middle−1=4−1=3>left=0,满足循环条件,继续循环

第2步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 3 − 0 2 ⌋ + 0 = 1 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{3-0}{2}\right\rfloor+0=1 middle =⌊2 right − left ⌋+ left =⌊23−0⌋+0=1

target = 3 > nums middle = nums 1 = 2 \text{target}=3>\text{nums}\\text{middle}=\text{nums}1=2 target=3>numsmiddle=nums1=2,故 left = middle + 1 = 1 + 1 = 2 < right = 3 \text{left}=\text{middle}+1=1+1=2<\text{right}=3 left=middle+1=1+1=2<right=3,满足循环条件,继续循环
第3步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 3 − 2 2 ⌋ + 2 = 2 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{3-2}{2}\right\rfloor+2=2 middle =⌊2 right − left ⌋+ left =⌊23−2⌋+2=2

target = 3 = nums middle = nums 2 = 3 \text{target}=3=\text{nums}\\text{middle}=\text{nums}2=3 target=3=numsmiddle=nums2=3,故 right = middle − 1 = 3 − 1 = 2 = left = 2 \text{right}=\text{middle}-1=3-1=2=\text{left}=2 right=middle−1=3−1=2=left=2,满足循环条件,继续循环

第4步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 2 − 2 2 ⌋ + 2 = 2 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{2-2}{2}\right\rfloor+2=2 middle =⌊2 right − left ⌋+ left =⌊22−2⌋+2=2

target = 3 = nums middle = nums 2 = 3 \text{target}=3=\text{nums}\\text{middle}=\text{nums}2=3 target=3=numsmiddle=nums2=3,故 right = middle − 1 = 2 − 1 = 1 < left = 2 \text{right}=\text{middle}-1=2-1=1<\text{left}=2 right=middle−1=2−1=1<left=2,不满足循环条件,跳出循环

由此可见,最后我们要效仿找有边界一样的循环外变量leftBorder应该被设置为left或者right+1,基于找右边界的逻辑,middle变动太大,不考虑。

1.4.2 target在数组范围中,且数组中不存在target

有如下如下这样一个升序数组nums,其target值为5:

第1步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 0 2 ⌋ + 0 = 4 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-0}{2}\right\rfloor+0=4 middle =⌊2 right − left ⌋+ left =⌊29−0⌋+0=4

target = 5 > nums middle = nums 4 = 4 \text{target}=5>\text{nums}\\text{middle}=\text{nums}4=4 target=5>numsmiddle=nums4=4,故 left = middle + 1 = 4 + 1 = 5 < right = 9 \text{left}=\text{middle}+1=4+1=5<\text{right}=9 left=middle+1=4+1=5<right=9,满足循环条件,继续循环

第2步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 9 − 5 2 ⌋ + 5 = 7 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{9-5}{2}\right\rfloor+5=7 middle =⌊2 right − left ⌋+ left =⌊29−5⌋+5=7

target = 5 < nums middle = nums 7 = 7 \text{target}=5<\text{nums}\\text{middle}=\text{nums}7=7 target=5<numsmiddle=nums7=7,故 right = middle − 1 = 7 − 1 = 6 > left = 5 \text{right}=\text{middle}-1=7-1=6>\text{left}=5 right=middle−1=7−1=6>left=5,满足循环条件,继续循环

第3步

middle = ⌊ right − left 2 ⌋ + left = ⌊ 6 − 5 2 ⌋ + 5 = 5 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{6-5}{2}\right\rfloor+5=5 middle =⌊2 right − left ⌋+ left =⌊26−5⌋+5=5

target = 5 < nums middle = nums 5 = 6 \text{target}=5<\text{nums}\\text{middle}=\text{nums}5=6 target=5<numsmiddle=nums5=6,故 right = middle − 1 = 6 − 1 = 5 = left = 5 \text{right}=\text{middle}-1=6-1=5=\text{left}=5 right=middle−1=6−1=5=left=5,满足循环条件,继续循环

第4步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 5 − 5 2 ⌋ + 5 = 5 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{5-5}{2}\right\rfloor+5=5 middle =⌊2 right − left ⌋+ left =⌊25−5⌋+5=5

target = 5 < nums middle = nums 5 = 6 \text{target}=5<\text{nums}\\text{middle}=\text{nums}5=6 target=5<numsmiddle=nums5=6,故 right = middle − 1 = 5 − 1 = 4 < left = 5 \text{right}=\text{middle}-1=5-1=4<\text{left}=5 right=middle−1=5−1=4<left=5,不满足循环条件,跳出循环

再加上middle:

由此可见,leftBorder在这种情况下,也是不知道设置成什么值比较好,但是由于找不到元素的情况不涉及target == numsmiddle,所以和找右侧边界的结果是一样的,继续往下看

1.4.3 target在数组范围的右边或者左边

假设有如下数组nums,target为1:

第1步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 3 − 0 2 ⌋ + 0 = 1 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{3-0}{2}\right\rfloor+0=1 middle =⌊2 right − left ⌋+ left =⌊23−0⌋+0=1

target = 1 < nums middle = nums 1 = 3 \text{target}=1<\text{nums}\\text{middle}=\text{nums}1=3 target=1<numsmiddle=nums1=3,故 right = middle − 1 = 3 − 1 = 2 > left = 0 \text{right}=\text{middle}-1=3-1=2>\text{left}=0 right=middle−1=3−1=2>left=0,满足循环条件,继续循环

第2步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 2 − 0 2 ⌋ + 0 = 1 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{2-0}{2}\right\rfloor+0=1 middle =⌊2 right − left ⌋+ left =⌊22−0⌋+0=1

target = 1 < nums middle = nums 1 = 3 \text{target}=1<\text{nums}\\text{middle}=\text{nums}1=3 target=1<numsmiddle=nums1=3,故 right = middle − 1 = 2 − 1 = 1 > left = 0 \text{right}=\text{middle}-1=2-1=1>\text{left}=0 right=middle−1=2−1=1>left=0,满足循环条件,继续循环

第3步
middle = ⌊ right − left 2 ⌋ + left = ⌊ 1 − 0 2 ⌋ + 0 = 0 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{1-0}{2}\right\rfloor+0=0 middle =⌊2 right − left ⌋+ left =⌊21−0⌋+0=0

target = 1 < nums middle = nums 0 = 2 \text{target}=1<\text{nums}\\text{middle}=\text{nums}0=2 target=1<numsmiddle=nums0=2,故 right = middle − 1 = 1 − 1 = 0 = left = 0 \text{right}=\text{middle}-1=1-1=0=\text{left}=0 right=middle−1=1−1=0=left=0,满足循环条件,继续循环

第4步

middle = ⌊ right − left 2 ⌋ + left = ⌊ 0 − 0 2 ⌋ + 0 = 0 \text { middle }=\left\lfloor\frac{\text { right }- \text { left }}{2}\right\rfloor+\text { left }=\left\lfloor\frac{0-0}{2}\right\rfloor+0=0 middle =⌊2 right − left ⌋+ left =⌊20−0⌋+0=0


target = 1 < nums middle = nums 0 = 2 \text{target}=1<\text{nums}\\text{middle}=\text{nums}0=2 target=1<numsmiddle=nums0=2,故 right = middle − 1 = 0 − 1 = − 1 < left = 0 \text{right}=\text{middle}-1=0-1=-1<\text{left}=0 right=middle−1=0−1=−1<left=0,不满足循环条件,跳出循环

此时的right为-1,再把middle的情况加上去:

它还是和1.3.3的情况是一样的,只不过,这次我们要考量一下,leftBorder到底应该设置为什么值,如果设置为left,那么,此时leftBorder为0,如果设置为right,那么leftBorder为-1,看似是成功设置一个不合理的值,但是1.4.1的情况又不成立,所以我们先保证target在数组范围中,且数组中存在target的情况,把leftBorder设置为left(或者right + 1)

1.4.4 找左边界总结

综上所述,我们设置循环外的变量leftBorder来记录左边界的值,即:

cpp 复制代码
    //找左边界
    int getLeftBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int leftBorder = -1; 
        //左闭右闭
        while (left <= right)
        {
            int middle = left + ((right - left) >> 1); //除2相当于右移1位,节省时间
            if (target <= nums[middle])
            {
                right = middle - 1; //target在左区间
            }
            else
            {
                left = middle + 1;
            }
            leftBorder = left; // 写leftBorder = right + 1也行
        }
        return leftBorder;
    }

1.5 总结与实现

我们似乎还没有解决找不到元素时,rightBorder和leftBorder应该设置什么值,不妨把它们联合起来思考,我们已经将leftBorder设置为left(或right+1),rightBorder设置为right(或left-1),先看"target在数组范围中,且数组中不存在target"的情况(例子还是上面的),直接看最后迭代的样子:

我们可以看到leftBorder和rightBoder如果按照上述规则设置,那么他们的差应该为1(或者leftBorder大于rightBorder),leftBorder大于rightBorder恰好是不符合常理的,所以这种情况应该返回{-1, -1}

再看"target在数组范围的右边或者左边"的情况(例子还是上面的),直接看最后迭代的样子:

我们可以看到,还是上面的一样的情况,leftBorder和rightBoder如果按照上述规则设置,那么他们的差应该为1(或者leftBorder大于rightBorder),leftBorder大于rightBorder恰好也是不符合常理的,所以这种情况应该返回{-1, -1}

所以,最后,我们的代码可以写成:

cpp 复制代码
class Solution {
public:
    //找右边界
    int getRightBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int rightBorder = -1; 
        //左闭右闭
        while (left <= right)
        {
            int middle = left + ((right - left) >> 1); //除2相当于右移1位,节省时间
            if (target >= nums[middle])
            {
                left = middle + 1;
            }
            else
            {
                right = middle - 1;
            }
            rightBorder = right; //写rightBorder = left - 1也行
        }
        return rightBorder;
    }
    //找左边界
    int getLeftBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int leftBorder = -1;
        //左闭右闭
        while (left <= right)
        {
            int middle = left + ((right - left) >> 1); //除2相当于右移1位,节省时间
            if (target <= nums[middle])
            {
                right = middle - 1; 
            }
            else
            {
                left = middle + 1;
            }
            leftBorder = left; // 写leftBorder = right + 1也行
        }
        return leftBorder;
    }
    //处理一下两种情况
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        if(leftBorder > rightBorder) // 或者写leftBorder - rightBorder == 1这个条件
        {
            return {-1, -1};
        }
        return{leftBorder, rightBorder};
    }
};

结果也是顺利通过:

相关推荐
智者知已应修善业30 分钟前
【51单片机8位数码管同时倒计时从9999】2024-1-25
c++·经验分享·笔记·算法·51单片机
洛水水32 分钟前
【力扣100题】86.柱状图中最大的矩形
算法·leetcode·职场和发展
渡之39 分钟前
GRiM-Net 深度解析 | 无人机 GNSS 拒止场景下两阶段跨视角视觉定位框架
深度学习·算法·动态规划·无人机
测试仪器廖生135902563851 小时前
罗德与施瓦茨 FSP13频谱分析仪FSP30
网络·人工智能·算法
happymaker06261 小时前
LeetCodeHot100——560.和为K的子数组
算法
dtq04241 小时前
C语言刷题数组5,6(求平均值,求最大值)
c语言·数据结构·算法
郭梧悠1 小时前
Hash算法入门Hash冲突解决方案
算法·哈希算法
洛水水2 小时前
【力扣100题】81.寻找两个正序数组的中位数
数据结构·算法·leetcode
happymaker06263 小时前
LeetCodeHot100——155.最小栈
算法
洛水水3 小时前
【力扣100题】85.每日温度
算法·leetcode·职场和发展