LeetCode-34. 在排序数组中查找元素的第一个和最后一个位置
1、题目描述:
给你一个按照非递减顺序排列的整数数组 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
2、代码:
#include <vector>
using namespace std;
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
// 如果数组为空,直接返回 [-1, -1]
if (nums.empty()) {
return vector<int>{-1, -1};
}
// 调用辅助函数 findFirst 查找目标值的第一个位置
int start = findFirst(nums, target);
// 调用辅助函数 findLast 查找目标值的最后一个位置
int end = findLast(nums, target);
// 返回结果,包含起始位置和结束位置
return vector<int>{start, end};
}
// 辅助函数:查找目标值的第一个位置
int findFirst(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1; // 定义左右边界
int mid;
// 开始二分查找
while (l <= r) {
mid = (l + r) / 2; // 计算中间值
// 如果找到目标值
if (target == nums[mid]) {
// 检查是否是第一个出现的位置
if (mid == 0 || nums[mid - 1] < target) {
return mid; // 确保这是第一个位置,返回索引
}
// 如果不是第一个位置,继续向左搜索
r = mid - 1;
}
// 如果目标值大于当前值,说明目标值在右侧,向右搜索
else if (nums[mid] < target) {
l = mid + 1;
}
// 如果目标值小于当前值,说明目标值在左侧,向左搜索
else {
r = mid - 1;
}
}
// 如果循环结束仍未找到目标值,返回 -1
return -1;
}
// 辅助函数:查找目标值的最后一个位置
int findLast(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1; // 定义左右边界
int mid;
// 开始二分查找
while (l <= r) {
mid = (l + r) / 2; // 计算中间值
// 如果找到目标值
if (target == nums[mid]) {
// 检查是否是最后一个出现的位置
if (mid == nums.size() - 1 || nums[mid + 1] > target) {
return mid; // 确保这是最后一个位置,返回索引
}
// 如果不是最后一个位置,继续向右搜索
l = mid + 1;
}
// 如果目标值大于当前值,说明目标值在右侧,向右搜索
else if (nums[mid] < target) {
l = mid + 1;
}
// 如果目标值小于当前值,说明目标值在左侧,向左搜索
else {
r = mid - 1;
}
}
// 如果循环结束仍未找到目标值,返回 -1
return -1;
}
};
3、解题思路:
1. 核心思想
由于数组是有序的,可以利用二分查找的特性:
- 起始位置 :目标值的第一个出现位置。
- 结束位置 :目标值的最后一个出现位置。
为了满足时间复杂度 O(logn) 的要求,我们需要分别对这两个位置进行两次独立的二分查找。(也就是说,可以用借助2个辅助函数来实现两个位置的查找)
2. 特殊情况处理
- 空数组 :如果
nums.empty()
,直接返回[-1, -1]
。 - 目标值不存在 :如果通过二分查找未找到目标值,返回
[-1, -1]
。
3. 具体步骤
第一步:查找起始位置
- 初始化左右边界
l = 0
和r = nums.size() - 1
。 - 使用二分查找:
- 计算中间值
mid = (l + r) / 2
。 - 如果
nums[mid] == target
:- 检查是否是第一个出现的位置(即
mid == 0
或nums[mid - 1] < target
)。 - 如果是,返回
mid
。 - 如果不是,说明前面可能还有目标值,继续向左搜索,更新右边界
r = mid - 1
。
- 检查是否是第一个出现的位置(即
- 如果
nums[mid] < target
,说明目标值在右侧,更新左边界l = mid + 1
。 - 如果
nums[mid] > target
,说明目标值在左侧,更新右边界r = mid - 1
。
- 计算中间值
- 如果循环结束仍未找到目标值,返回
-1
。
第二步:查找结束位置
- 初始化左右边界
l = 0
和r = nums.size() - 1
。 - 使用二分查找:
- 计算中间值
mid = (l + r) / 2
。 - 如果
nums[mid] == target
:- 检查是否是最后一个出现的位置(即
mid == nums.size() - 1
或nums[mid + 1] > target
)。 - 如果是,返回
mid
。 - 如果不是,说明后面可能还有目标值,继续向右搜索,更新左边界
l = mid + 1
。
- 检查是否是最后一个出现的位置(即
- 如果
nums[mid] < target
,说明目标值在右侧,更新左边界l = mid + 1
。 - 如果
nums[mid] > target
,说明目标值在左侧,更新右边界r = mid - 1
。
- 计算中间值
- 如果循环结束仍未找到目标值,返回
-1
。
4. 时间与空间复杂度分析
- 每次二分查找的时间复杂度为 O(logn)。
- 分别查找起始位置和结束位置,总共进行了两次二分查找,总时间复杂度仍为 O(logn)。
- 算法只使用了常量级别的额外空间(如变量
l
,r
,mid
),因此空间复杂度为 O(1)。