【代码随想录刷题记录】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=nums[middle]=nums[4]=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 == nums[middle]这个条件必须融到target > nums[middle]或者target < nums[middle]这两个条件中(取并集),原因是当target == nums[middle]时,它只能找到连续重复序列的中间位置的元素,就退出循环了,为了不让它退出循环继续让middle移动,所以我们要重新审视一下if else的条件,如果target == nums[middle]与target > nums[middle]做并集得到target >= nums[middle]这样的条件,那么重新来一遍这个查找的过程:

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=nums[middle]=nums[4]=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<nums[middle]=nums[7]=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=nums[middle]=nums[5]=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=nums[middle]=nums[6]=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>nums[middle]=nums[4]=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<nums[middle]=nums[7]=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<nums[middle]=nums[5]=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<nums[middle]=nums[5]=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<nums[middle]=nums[1]=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<nums[middle]=nums[1]=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<nums[middle]=nums[0]=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<nums[middle]=nums[0]=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 == nums[middle]这个条件改为target <= nums[middle]这样的条件,即循环框架大概是这样的:

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=nums[middle]=nums[4]=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>nums[middle]=nums[1]=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=nums[middle]=nums[2]=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=nums[middle]=nums[2]=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>nums[middle]=nums[4]=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<nums[middle]=nums[7]=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<nums[middle]=nums[5]=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<nums[middle]=nums[5]=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 == nums[middle],所以和找右侧边界的结果是一样的,继续往下看

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<nums[middle]=nums[1]=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<nums[middle]=nums[1]=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<nums[middle]=nums[0]=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<nums[middle]=nums[0]=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};
    }
};

结果也是顺利通过:

相关推荐
秋夫人13 分钟前
B+树(B+TREE)索引
数据结构·算法
代码雕刻家42 分钟前
数据结构-3.1.栈的基本概念
c语言·开发语言·数据结构
梦想科研社1 小时前
【无人机设计与控制】四旋翼无人机俯仰姿态保持模糊PID控制(带说明报告)
开发语言·算法·数学建模·matlab·无人机
Milo_K1 小时前
今日 leetCode 15.三数之和
算法·leetcode
Darling_001 小时前
LeetCode_sql_day28(1767.寻找没有被执行的任务对)
sql·算法·leetcode
AlexMercer10121 小时前
【C++】二、数据类型 (同C)
c语言·开发语言·数据结构·c++·笔记·算法
Greyplayground1 小时前
【算法基础实验】图论-BellmanFord最短路径
算法·图论·最短路径
蓑 羽1 小时前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
源代码:趴菜1 小时前
LeetCode63:不同路径II
算法·leetcode·职场和发展
儿创社ErChaungClub1 小时前
解锁编程新境界:GitHub Copilot 让效率翻倍
人工智能·算法