在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

1
2
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

1
2
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

1
2
输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums 是一个非递减数组
  • -10^9 <= target <= 10^9

注意到题目中提到数组元素按非递减顺序排序,则可以采用二分查找法。

首先实现方法int lowerBound(int[] nums, int target):给定一个按照非递减顺序排列的整数数组 nums,和一个目标值 target,找出第一个大于等于target的数的位置。那么可以将其他问题使用该方法解决:

  • 找出第一个大于target的数的位置:lowerBound(nums, target+1)
  • 找出最后一个小于target的数位置:lowerBound(nums, target)-1

  • 找出最后一个小于等于target的数位置:lowerBound(nums, target+1)-1

这里使用两侧开区间的循环不变式,即(left, right),循环不变式中所有在left以及left左边的数都必定严格小于target,所有在right以及right右边的数都必定大于等于target,则代码可写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
/**
* 求解非递减nums数组中第一个大于等于target的数的索引(此处使用开区间)
* @param nums 非递减整数数组
* @param target 目标值
* @return 目标值的索引
*/
public int lowerBound(int [] nums, int target) {
int left = -1, right = nums.length, mid;
while (left + 1 < right) { // (left, right)当left+1 == right时就没有元素了,退出循环
mid = left + (right - left) / 2; // 防止整形溢出
if (nums[mid] < target) { // < 保证left以及left左边的数都是严格小于target
left = mid; // (mid+1, right)
}
else { // 即nums[mid] >= target 保证right右边的数都是大于等于target
right = mid; // (left, mid)
}
}
return right;
}

public int[] searchRange(int[] nums, int target) {
int start = lowerBound(nums, target); // 求第一个大于等于target的数的索引
if (start == nums.length || nums[start] != target) {
return new int[] {-1, -1};
}

// 注意上面start判断存在的话 表明target存在 则end必然存在
// 求第一个大于等于target+1的数的索引前一个索引 即最后一个小于等于target的数的索引
int end = lowerBound(nums, target + 1) - 1;
return new int[] {start, end};
}
}