34. 在排序数组中查找元素的第一个和最后一个位置
难度 中等
给定一个按照升序排列的整数数组 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] <= 109nums是一个非递减数组-109 <= target <= 109
题解
这道题应该是二分查找的一个增强版,简单的二分查找是在一个升序无重复的数组中查找,这个增强版是在升序重复的数组中查找。根据题目意思,需要找到target的第一个位置和最后一个位置。如果我们直接用二分查找,可能出现三种结果,在第一个位置,在第一个位置和最后一个位置的中间,在最后一个位置。但是这道题目不需要中间位置,所以要对我们的二分查找进行修改。
问题是如何进行修改,要定下一个标准,去掉中间位置,还有第一个位置或者最后一个位置,再去掉其中一种可能,我们就可以解决问题的方案确定了。如果我们去掉最后一个位置,每次使用二分修改版,只会查到第一个位置,我们就完成题目的一半了。那最后的位置又如果确定,我们可以查找大于target的数第一个位置,然后再减一。
-
修改版二分查找
- 首先没有了nums[mid] == target,这样就避免了取到中间位置的指针的情况
-
nums[mid] > target || (lower && nums[mid] >= target),这段代码左右条件只要一个成立就成立,
- nums[mid] > target,这表示中间指针的值大于target,
- (lower && nums[mid] >= target),这表示当lower为true且nums[mid] >= target都成立时才成立
我们可以带入代码验证一下
-
当lower为false时,只要nums[mid] > target整个if都成立,会执行下面的代码
end = ans - 1; ans = mid;实现了指针左移,会直到while结束,此时的target刚刚是大于target的第一个位置
-
当lower为true时,我们发现,只要nums[mid] >= target,整个if都成立,会执行下面的代码
end = ans - 1; ans = mid;- 实现了指针左移,跟上面不同的是,会取到nums[mid]=target这种情况,会直到nums[mid]<target,此时的ans刚好是target第一位位置
class Solution {
public int[] searchRange(int[] nums, int target) {
int start = search(nums, target, true);//查找第一个位置
int end = search(nums, target, false) - 1;//查找大于target的数第一个位置,再减一
if(start <= end && end < nums.length && nums[start] == target && nums[end] == target){//条件判断
return new int[]{start, end};
}
return new int[]{-1, -1};
}
public int search(int[] nums, int target, boolean lower){
int start = 0;//左指针
int end = nums.length - 1;//右指针
int ans = nums.length;
while(end >= start){//右指针大于左指针
int mid = start + ((end - start) / 2);//中间指针
if(nums[mid] > target || (lower && nums[mid] >= target)){//见上面文字分解
end = ans - 1;
ans = mid;
}else{
start = mid + 1;
}
}
return ans;
}
}