【小白爬Leetcode34】6.1 在排序数组中查找元素的第一个和最后一个位置 Find First and Last Position of Element in Sorted Array
Leetcode34
m
e
d
i
u
m
\color{#FF4500}{medium}
medium
点击进入原题链接:Leetcode34 在排序数组中查找元素的第一个和最后一个位置 Find First and Last Position of Element in Sorted Array
题目
Discription
Given an array of integers nums
sorted in ascending order, find the starting and ending position of a given target
value.
Your algorithm’s runtime complexity must be in the order of O(log n).
If the target is not found in the array, return [-1, -1]
.
中文描述
给定一个按照升序排列的整数数组 nums
,和一个目标值 target
。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
思路 两次二分查找:
由于使用常数次二分查找,因此时间复杂度为O(logn),没有用到额外容器,空间复杂度为O(1)。
实现一:
一次二分查找找左端点,一次二分查找找右端点。
在查找左端点的时候,和一般二分查找有区别的地方在于:
- 当遇到
target == nums[mid]
的时候,需要判断它是否是左端点,如果mid==0 || nums[mid-1]<target
,意味着当前mid
到达了数组的边界或者mid
的前一个元素的值已经不再是target
了,则说明左端点找到了。 - 如果当前的
target == nums[mid]
不是左端点,也就是左边还有值等于target
的元素,那么此时应该缩小搜索空间,令end = mid-1;
- 如果
target != nums[mid]
,则和一般的二分查找一样,缩小搜索范围直到找到target != nums[mid]
或者begin>end
,如果是后者,说明该数组里没有值为target
的元素,那么按照返回-1用来构成最终的答案。
同理,在查找右端点的时候,和一般二分查找有区别的地方在于:
- 当遇到
target == nums[mid]
的时候,需要判断它是否是右端点,如果mid==nums.size()-1 || nums[mid+1]>target
,意味着当前mid
到达了数组的边界或者mid
的后一个元素的值已经不再是target
了,则说明右端点找到了。 - 如果当前的
target == nums[mid]
不是右端点,也就是右边还有值等于target
的元素,那么此时应该缩小搜索空间,令begin= mid+1;
- 如果
target != nums[mid]
,则和一般的二分查找一样,缩小搜索范围直到找到target != nums[mid]
或者begin>end
,如果是后者,说明该数组里没有值为target
的元素,那么按照返回-1用来构成最终的答案。
完整代码如下:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left_range = searchLeft(nums,target);
if(left_range==-1) return *(new vector<int>(2,-1));
int right_range = searchRight(nums,target); //如果查找左边界没找到target,那么右查找肯定也找不到,因为左查找和右查找只有在找到了target的情况下才有不同的处理,找到target之前二者是一样的,所以此时直接返回[-1,-1]即可。
return *(new vector<int>{left_range,right_range});
}
private:
int searchLeft(vector<int>& nums, int target){
int begin = 0;
int end = nums.size()-1;
while(begin<=end){
int mid = (begin+end)/2;
if(target == nums[mid]){
if(mid==0 || nums[mid-1]<target){//如果是边界了
return mid;
}
end = mid-1; //如果不是边界,把右边界缩小到mid-1
}
//下面和普通的二分查找一样了
else if(target<nums[mid]){
end = mid-1;
}
else begin = mid+1;
}
//如果没找到,说明该元素不存在,直接返回-1
return -1;
}
int searchRight(vector<int>& nums, int target){
int begin = 0;
int end = nums.size()-1;
while(begin<=end){
int mid = (begin+end)/2;
if(target == nums[mid]){
if(mid==nums.size()-1 || nums[mid+1]>target){//如果是边界了
return mid;
}
begin = mid+1; //如果不是边界,把右边界缩小到mid-1
}
//下面和普通的二分查找一样了
else if(target<nums[mid]){
end = mid-1;
}
else begin = mid+1;
}
//如果没找到,说明该元素不存在,直接返回-1
return -1;
}
};
实现二
实现一有一个小问题,第一次找到值为target
的元素的这个过程重复了一次,实现二针对这个问题进行了一个改良,首先先找到值为target
的那个元素,再以这个点为边界向左和向右去查找边界。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int begin = 0;
int end = nums.size()-1;
int first_target_idx = -1; //如果找到了target那么就是target的索引,没找到就是-1
//先找到target再说
while(begin<=end){
int mid = (begin+end)/2 ;
if(target == nums[mid]){
first_target_idx = mid;
break;
}
else if(target<nums[mid]) end = mid-1;
else begin = mid+1;
}
//如果没找到target可以直接返回[-1,-1]了
if(first_target_idx==-1) return vector<int>(2,-1);
//初始化左右边界为第一个找到的target点
int left_range = first_target_idx;
int right_range = first_target_idx;
//记录下此时begin和end的位置备用
int pre_begin = begin;
int pre_end = end;
while(left_range-1>=0 && nums[left_range-1]==target){ //左查找
end = left_range-1;
while(begin<=end){
int mid = (begin+end)/2 ;
if(target == nums[mid]){
left_range = mid;
break;
}
else if(target<nums[mid]) end = mid-1;
else begin = mid+1;
}
}
end = pre_end;
while(right_range+1<nums.size() && nums[right_range+1]==target){ //右查找
begin = right_range+1;
while(begin<=end){
int mid = (begin+end)/2 ;
if(target == nums[mid]){
right_range = mid;
break;
}
else if(target<nums[mid]) end = mid-1;
else begin = mid+1;
}
}
//返回左查找和右查找的结果
return vector<int>{left_range,right_range};
}
};
不知道哪些很快的解答是怎么来的…