题目地址
最近忙活实验,实在没空刷题,这个题对我来说难度还蛮大的,尤其是理解那个找左边界和找右边界的条件,后来我按照自己的理解写了出来(感觉给的答案解释起来有点反认识规律),所以我从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向右移动找到右边界,回顾一下二分查找(左闭右闭)的代码:
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;
}
};
这段代码的核心功能就是这个循环:
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]这样的条件,那么重新来一遍这个查找的过程:
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,似乎不合理,但是我们先忘掉这种“不合理”,按照现在整理出来的思路,将找右边界的函数写出来,即:
//找右边界
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]这样的条件,即循环框架大概是这样的:
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来记录左边界的值,即:
//找左边界
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}
所以,最后,我们的代码可以写成:
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};
}
};
结果也是顺利通过: