题目:
34. 在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例一:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例二:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例三:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
- 0 <= nums.length <= 105
- 109 <= nums[i] <= 109
- nums 是一个非递减数组
- 109 <= target <= 109
思路:
由题中给出用时间复杂度为O(log n)的算法和给出的数组有序可以想到用二分法查找。
方法一:
寻找target的区间范围有三种情况:
- target在数组范围的左边或者右边,例如数组[2,4,6],target为 1 ,返回[-1,-1]。
- target在数组范围内,且target不在数组中,例如数组[2,4,6],target为 3,返回[-1,-1]。
- target在数组范围内且target在数组中,例如数组[2,4,6],target为 4 ,返回[1,1]。
采用二分法来寻找target的左边界和右边界。用两个函数分别寻找左边界和右边界适合对二分法还不熟悉的人,避免被绕进去。
寻找左边界
//二分法寻找target的左边界,不包含target
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int leftBorder = -2;//记录leftBorder没有被赋值的情况
while(left <= right) {//左闭右闭区间
int mid = left + ((right - left)/2);
if(nums[mid] < target)
left = mid + 1;
else {
right = mid - 1;
leftBorder = right;//当nums[mid]==target时,更新right
}
}
return leftBorder;
}
寻找右边界
//二分法寻找target的右边界,不包括target
int getRightBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int rightBorder = -2;//记录rightBorder没有被赋值的情况
while(left <= right) {//左闭右闭区间
int mid = left + ((right - left)/2);
if(nums[mid] > target)
right = mid - 1;
else {
left = mid + 1;
rightBorder = left;//当nums[mid]==target时,更新left
}
}
return rightBorder;
}
代码:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int rightBorder = getRightBorder(nums, target);
int leftBorder = getLeftBorder(nums, target);
//情况1
if(rightBorder == -2 || leftBorder == -2) return {-1,-1};//
//情况3
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
//情况2
return {-1,-1};
}
private:
//寻找target的右边界
int getRightBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int rightBorder = -2;
while(left <= right) {//左闭右闭区间
int mid = left + ((right - left)/2);
if(nums[mid] > target)
right = mid - 1;
else {
left = mid + 1;
rightBorder = left;
}
}
return rightBorder;
}
//寻找target的左边界
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int leftBorder = -2;
while(left <= right) {//左闭右闭区间
int mid = left + ((right - left)/2);
if(nums[mid] < target)
left = mid + 1;
else {
right = mid - 1;
leftBorder = right;
}
}
return leftBorder;
}
};
复杂度分析:
- 时间复杂度:O(logn)
- 空间复杂度:O(1)
方法二:
使用两次二分查找,第一次查找>=target的位置,第二次查找>target出现的位置
代码:
class Solution {
public:
//先找>=target的第一个
//再找>target的第一个
vector<int> searchRange(vector<int>& nums, int target) {
int left = binarySearch(nums, target);
int right = binarySearch(nums, target + 1);
if(left == nums.size() || nums[left] != target)
return {-1,-1};
return {left, right - 1};
}
private:
int binarySearch(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while(left < right) {
int mid = left + ((right - left)/2);
if(nums[mid] < target)
left = mid + 1;
else
right = mid;
}
return left;
}
};
复杂度分析:
- 时间复杂度:O(logn)
- 空间复杂度:O(1)