1. 题目链接:34. 在排序数组中查找元素的第一个和最后一个位置
2. 题目描述:
给你一个按照非递减顺序排列的整数数组
nums
,和一个目标值target
。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值
target
,返回[-1, -1]
。你必须设计并实现时间复杂度为
O(log n)
的算法解决此问题。示例 1:
输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1]
示例 3:
输入:nums = [], target = 0 输出:[-1,-1]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums
是一个非递减数组-109 <= target <= 109
3. 寻找左边界的思路:
- 用
x
表示该元素,resLest
表示左边界,resRight
表示右边界 - 左边区间都是小于
x
的 - 右边区间(包括左边界)都是大于等于
x
的 mid
的落点情况如下:- 当
mid
落在[left,resLeft-1]
区间的时候,也就是arr[mid]<target
,说明[left,mid]
都是可以舍去的,此时更新left
到mid+1
的位置,继续在[mid+1,right]
上寻找左边界 - 当
mid
落在[resLeft,right]
的区间的时候,也就是arr[mid]>=target
,说明[mid+1,right]
(因为mid
可能是最终结果,不能舍去)是可以舍去的,此时更新right
到mid
的位置,继续[left,mid]
上寻找左边界
- 当
- 由此就可以通过二分来快速寻找左边界
注意事项:找中间元素需要向下取整
因为后续继续移动指针的时候:
左指针:
left=mid+1
,是会向后移动的,因此区间是会缩小的右指针:
right=mid
,可能会原地踏步(比如:如果向上取整的话,如果剩下1
,2
两个元素的,left==1
,right==2
,mid==2
。更新区间之后,left
,right
,mid
的值没有改变,就会陷入死循环)
4. 寻找有右边界的思路:
- 用
x
表示该元素,resLest
表示左边界,resRight
表示右边界 - 左边区间(包括右边界)
[left,resRight]
都是小于等于x
的 - 右边区间
[resRight+1,right]
都是大于x
是的 mid
的落点情况如下:- 当我们的
mid
落在[left,resRight]
区间的时候,也就是arr[mid]<=target
,说明[left,mid-1]
(mid
不可以舍去,因为有可能是最终结果)都是可以舍去的,此时更新left
到mid
的位置 - 当
mid
落在[resRight+1,right]
的区间的时候,也就是arr[mid]>target
,说明[mid,right]
内的元素是可以舍去的,此时更新right
到mid-1
的位置
- 当我们的
- 由此就可以通过二分来快速寻找右边界
注意事项:找中间元素需要向上取整
因为后续继续移动指针的时候:
左指针:
left = mid
,可能会原地踏步(⽐如:如果向下取整的话,如果剩下1
,2
两个元素,
left == 1, right == 2,mid == 1
。更新区间之后,left,right,mid
的值没有改变,就会陷⼊死循环)。
右指针:
right = mid - 1
,是会向前移动的,因此区间是会缩⼩的;因此⼀定要注意,当
right = mid
的时候,要向下取整。
5. 算法流程:
- 首先判断数组是否为空,如果为空则返回{-1, -1}表示无解。
- 初始化左指针
left
为0,右指针right
为数组长度减1。 - 通过二分查找找到目标元素的起始位置。循环条件是左指针
left
小于右指针right
,计算中间位置mid
,如果中间位置的元素小于目标元素,则目标元素可能在右半部分,更新左指针left
为mid + 1
。如果中间位置的元素大于等于目标元素,则目标元素可能在左半部分,更新右指针right
为mid
。循环结束后,左指针left
指向的位置就是目标元素的起始位置。 - 判断数组中是否存在目标元素,如果左指针
left
指向的元素不等于目标元素,则不存在目标元素,返回{-1, -1}表示无解。 - 将目标元素的起始位置保存在变量
begin
中。 - 通过二分查找找到目标元素的结束位置。重新初始化左指针
left
为0,右指针right
为数组长度减1。循环条件是左指针left
小于右指针right
,计算中间位置mid
,如果中间位置的元素大于目标元素,则目标元素可能在左半部分,更新右指针right
为mid - 1
。如果中间位置的元素小于等于目标元素,则目标元素可能在右半部分,更新左指针left
为mid
。循环结束后,右指针right
指向的位置就是目标元素的结束位置。 - 返回包含起始位置和结束位置的结果数组{begin, right}作为最终答案。
6. C++算法代码:
c++
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
//处理边界情况
if(nums.size()==0) return {-1,-1};
int begin=0;
//二分左端点
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<target) left=mid+1;
else right=mid;
}
//判断的是否有结果
if(nums[left]!=target)
return {-1,-1};
else begin=left;
//二分右端点
left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left+1)/2;
if(nums[mid]>target) right=mid-1;
else left=mid;
}
return {begin,right};
}
};