给你一个按照非递减顺序排列的整数数组 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
方法一:
cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> result = {-1, -1};
int left = 0;
int right = nums.size() - 1;
while (left <= right) { //二分查找算法的核心部分
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (left < nums.size() && nums[left] == target) {
result[0] = left; //找到的话就把起始值记录为left
} else {
return result; //到了数组结尾还没找到那就直接返回-1,-1
}
right = nums.size() - 1; //重置 right 为数组的最后一个索引。
while (left <= right) { //第二次二分查找
int mid = left + (right - left) / 2;
if (nums[mid] <= target) { //这里条件不一样需要注意
left = mid + 1;
} else {
right = mid - 1;
}
}
result[1] = right; //更新终止值
return result;
}
};
这道题可以使用二分查找的原因主要在于题目中的数组是非递减顺序排列的整数数组
vector<int> result = {-1, -1};
初始化一个整数向量 result
,其初始值为 {-1, -1}
。
int mid = left + (right - left) / 2;
计算当前查找范围的中间索引 mid
。这里采用的计算方式是为了避免可能的整数溢出。
if (nums[mid] < target)
:
如果 nums[mid]
小于 target
,说明目标值位于 mid
的右侧,因此将 left
移动到 mid + 1
,缩小查找范围。
如果 nums[mid]
大于或等于 target
,则目标值位于 mid
的左侧(包括 mid
本身),所以将 right
移动到 mid - 1
,缩小查找范围。
left < nums.size()
:
- 这个条件确保
left
不会超出nums
数组的范围。因为left
在查找的过程中可能已经移动到了数组的末尾,如果left
超过了数组的索引范围,直接访问nums[left]
会导致运行时错误。
nums[left] == target
:
- 这个条件检查
nums[left]
是否等于target
。如果left
所指向的元素等于目标值,说明找到了目标值的起始位置。此时,将result[0]
更新为left
,即目标值在数组中的起始索引。
如果 left
不在有效范围内,或者 nums[left]
不等于 target
,这说明数组中不存在目标值。此时直接返回 result
,它的值仍然是 [-1, -1]
,表示未找到目标值。
为什么两次二分查找的 if 语句不一样:
在第一次二分查找中,我们的目标是找到 target
的起始位置。如果存在起始值,当 left
和 right
相遇时的相遇点即为起始值 target,这个时候需要保证 left 为 target,就需要right
左移来退出循环。
而在第二次二分查找中,我们的目标是找到 target
的结束位置。我们需要保证right 为 target,就需要 left右移来退出循坏。
二分查找的精髓通过每次比较中间值来逐步缩小查找范围,保证时间复杂度为 O(logn)
方法二:
cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int start = find(nums, target);
int end = find(nums, target + 1) - 1;
if (start == -1 || end < start) {
return {-1, -1};
}
return {start, end};
}
int find(vector<int>& nums, int target) {
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 - 1;
}
}
return left;
}
};
利用 find(nums, target)
找到 target
的第一个位置,再利用 find(nums, target + 1)
找到比 target
大的第一个元素的位置,从而间接确定 target
的结束位置。
如果数组中不存在第一个大于 target
的元素,那么 find(nums, target + 1)
的结果将会是 nums.size(),
end的值为 nums.size() - 1
假设 nums = [5, 7, 7, 8, 8, 10]
,target = 8
:
find(nums, 8)
返回3
,因为8
的第一个位置在索引3
。find(nums, 9)
返回5
,因为9
比8
大,且索引5
是第一个大于8
的位置。end = find(nums, 9) - 1
等于4
,所以返回[3, 4]
。