给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题想到的第一种方法自然是二分查找,但是二分查找只能找到一个节点,不能定位的某个节点的首位节点,但既然可以定位到这个节点,那么就可以以这个节点为中心,两遍扩散寻找到首尾范围。
class Solution {
public int[] searchRange(int[] nums, int target) {
int flag = findTarget(nums, target);
if ((flag == -1) || (nums.length == 0)) {
return new int[]{-1, -1};
}
int left = flag;
int right = flag;
while ((left >= 0)&&(nums[left] == target)) {
left--;
}
while ((right < nums.length) && (nums[right] == target)) {
right++;
}
return new int[]{left + 1, right - 1};
}
private int findTarget(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
}
上面一种方法并不是完美的二分查找,如果在找左右边界的时候也能够使用二分查找那就完美符合题目的条件。但是在自己思路上有一个难题,那就是如何确定是向左转移还是向右转移,无疑,写两个方法是最容易想到的方法,一位大佬已经提供了这种方法——地址
下面是这种解法的详解,最好是这样写,能最大限度的理解二分查找。
首先二分查找只能找到一个点,但是这个题是要找到两个点,那么以上一种的二分为思路,首先找到一个,然后将数组分为两部分,前后分别二分,分别写出两种方法的查找方法。
在写相关的方法时候,一定要以正确的例子为参照物,然后写解法,最后考虑极端情况。
首先来看如何写出first点的方法
第一步先打出框架来,二分查找框架都一样,但是条件的不一样,得出的解法思路是不一样的,这里以大佬的思路为基础。while循环的条件是left<right那么,最后退出循环的结果一定是left=right,
那么最后返回left还是right都是一样的。
接着是循环中的判断条件的填写,在寻找first中的条件,继续以大佬的思路为基础讲解。
if (nums[mid] < target)
如果满足这一条件,那么在区间[left,mid]一定不存在targe,放心的写 left = mid + 1
;
然后是else中的,也就是nums[mid] >= target
的情况,那么就分情况来分析
首先如果nums[mid]>target
,那么这个时候可以大胆的写 right= mid -1;
如果nums[mid] = target
,这个时候呢?因为这里是要找first,能确定mid处就是first吗?不能,所以mid得保留,另外,在mid之后的位置一定是大于target的。
综上-> 下一步要找的区间是[left,mid]。
看起来确实是这样,但是我为什么要把if的条件设置成 nums[mid]<target
,设置成<=不行吗?行不行我没试过,以我的理解就是,这里是要找左边界,那么得确保找到点的左边一定小于target,那么这里设置成小于是最合适。
最后还要一点就是,如果没有找到target怎么办?这时候返回-1,找到了的话,left=right,随便返回哪个都可以
int left = 0;
int right = nums.length - 1;
while(left<right){
int mid = (left+right)/2;
if(nums[mid]<target){
left = mid + 1;
}else{
right = mid;
}
}
if(nums[left]==target) return left;
return -1;
接着看findLast方法,分析思路和findFirst一样。由于要找右边界,那么得确保右边所有的节点都比target大
那么if条件设置成
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid;
}
最后直接返回left或者right,因为在前一步寻找first中,已经确定了数组中是否有target,如果没有直接就返回[-1,-1]了
class Solution {
public int[] searchRange(int[] nums, int target) {
int length = nums.length;
if (length == 0) {
return new int[]{-1, -1};
}
int left = findFirst(nums, target);
if (left == -1) {
return new int[]{-1, -1};
}
int right = findLast(nums, target);
return new int[]{left, right};
}
private int findLast(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = (left + right + 1) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid;
}
}
return left;
}
private int findFirst(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
if (nums[left] == target) {
return left;
} else return -1;
}
}
但是官方解答只用了一个方法,巧妙的是官方解答用了一个flag,以这个flag来确定想左走还是向右走。首先看searchRange。int leftIdx = extremeInsertionIndex(nums, target, true);
在extremeInsertionIndex方法中,第三个参数是boolean left,简明扼要的说就是如果已经找到过左边接了,直降传递false,否则传递true,这有什么作用?之后便会看到。但是这里有一个不明白的地方就是为什么要将hi置为length而不是length-1???
首先判断nums[mid]是否比target大,如果前者大那么将右指针置为mid。这是要向左边走的第一种情况。另外一种情况就是找到了一个target节点,要寻找左边界,那么这个时候也需要向左走,以此出来了另一个条件left && target == nums[mid]
;如果不符合这两个条件无疑就得向右走。
class Solution {
// returns leftmost (or rightmost) index at which `target` should be
// inserted in sorted array `nums` via binary search.
private int extremeInsertionIndex(int[] nums, int target, boolean left) {
int lo = 0;
int hi = nums.length;
while (lo < hi) {
int mid = (lo + hi) / 2;
if (nums[mid] > target || (left && target == nums[mid])) {
hi = mid;
}
else {
lo = mid+1;
}
}
return lo;
}
public int[] searchRange(int[] nums, int target) {
int[] targetRange = {-1, -1};
int leftIdx = extremeInsertionIndex(nums, target, true);
// assert that `leftIdx` is within the array bounds and that `target`
// is actually in `nums`.
if (leftIdx == nums.length || nums[leftIdx] != target) {
return targetRange;
}
targetRange[0] = leftIdx;
targetRange[1] = extremeInsertionIndex(nums, target, false)-1;
return targetRange;
}
}