力扣的第34题 在排序数组中查找元素的第一个和最后一个位置
1.题目
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
1.1 示例
输入: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]
1.2 提示
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums
是一个非递减数组-109 <= target <= 109
2. 暴力思路
首先想到暴力,遍历整个数组然后把符合要求的存入一个 List 中,然后把集合中第一个和最后一个元素封装成数组返回。想想当题目元素只有一个时,new int[]{list.get(0), list.get(list.size() - 1)} 也能满足需求。 但这样时间复杂度为O(n) 了,但提交也能过。
2.1 代码
public int[] searchRange(int[] nums, int target) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
list.add(i);
}
}
return convert(list);
}
private int[] convert(List<Integer> list) {
if (list == null || list.isEmpty()) {
return new int[]{-1, -1};
}
return new int[]{list.get(0), list.get(list.size() - 1)};
}
3 二分优化
3.1 思路
题目说数组是递增的,用 left 和 right 两个指针来划定搜索范围,mid 是中间元素的索引。如果中间元素等于目标值,那可能这个元素就是第一个出现的位置,但也可能存在更早的相同元素,所以需要把 right 调整到 mid - 1,继续往左找。如果中间元素小于目标值,说明要找的元素在右边,left 调整到 mid + 1;否则,就在左边找。这个过程不断缩小范围,直到找到最左边的目标元素。
同理找最后的方法是一致的。
代码是通过两次二分查找分别找到目标元素的最左和最右出现的位置。这种方式的时间复杂度是 O(log n),比线性扫描的 O(n) 要高效得多。
3.2 代码
public int[] searchRange(int[] nums, int target) {
int first = findFirst(nums, target);
int last = findLast(nums, target);
return new int[]{first, last};
}
private int findFirst(int[] nums, int target) {
int left = 0, right = nums.length - 1;
int first = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
first = mid;
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return first;
}
private int findLast(int[] nums, int target) {
int left = 0, right = nums.length - 1;
int last = - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
last = mid;
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return last;
}