本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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]
思路
滑动窗口算法时间复杂度: O(n)
先定义两个指针,一个 l 一个 r
l指针移动到第一个符合目标数值target的位置
然后 r 指针 从l指针的位置向后遍历,直到最后一个符合目标数值target的位置
二分算法
时间复杂度: O(logn)
直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 target 的下标,但这个方法的时间复杂度为 O(n),没有利用到数组升序排列的条件。
由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。
考虑 target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target 的位置」(记为 leftIdx)和「第一个大于 target 的位置减一」(记为 rightIdx)。
二分查找中,寻找 leftIdx 即为在数组中寻找第一个大于等于 target 的下标,寻找 rightIdx 即为在数组中寻找第一个大于 target 的下标,然后将下标减一。两者的判断条件不同,为了代码的复用,我们定义 binarySearch(nums, target, lower) 表示在 nums 数组中二分查找 target 的位置,如果 lower 为 \rm true,则查找第一个大于等于 target 的下标,否则查找第一个大于 target 的下标。
最后,因为 target 可能不存在数组中,因此我们需要重新校验我们得到的两个下标 leftIdx 和 rightIdx,看是否符合条件,如果符合条件就返回 [leftIdx,rightIdx],不符合就返回 [−1,−1]。
ac代码
class Solution {
public int[] searchRange(int[] nums, int target) {
int l = -1,r = -1;
for(int i = 0;i < nums.length;i++){
if(nums[i] == target){ //判断第一个左指针的位置
l = i;
break;
}
}
if(l == nums.length - 1) r = l; //如果数组中的最后一个数是第一个目标值target 那么r = l
if(l != -1){
for(int i = l;i < nums.length;i++){
if(nums[i] != target){ //如果不等于目标值target 表明他的前一位一定是最后一个目标值targe
r = i - 1;
break;
}else if(nums[i] == target && i == nums.length - 1) r = i;
//如果 数组的最后一个数是目标数值 target
}
}
return new int[]{l, r};
}
}
二分算法
class Solution {
public int[] searchRange(int[] nums, int target) {
int leftIdx = binarySearch(nums, target, true);
int rightIdx = binarySearch(nums, target, false) - 1;
if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
return new int[]{leftIdx, rightIdx};
}
return new int[]{-1, -1};
}
public int binarySearch(int[] nums, int target, boolean lower) {
int left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
}