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] <= 109
nums
是一个非递减数组-109 <= target <= 109
解题思路
一开始想到了快慢指针法,一个保留给定目标值的开始位置,一个寻找给定目标值的结束位置,但这样的时间复杂度显然是 O(n),不满足题目需求。
因为数组有序且要求时间复杂度为 O(log n),考虑一下二分法。
我的思路:
- 基础的二分查找中,我们找到 target 时算法就结束了,但这题我们需要做些处理
- 如果我们想找最左边等于 target 的值,那么一开始在数组中找到 target 的时候,并不代表我们已经找到了左边界
- 虽然此时 middle 指向的值等于 target 了,但是我们要找的值可能还在 middle 左边
- 为了总体上达到 O(log n) 的时间复杂度,我们选择继续使用二分法,做法是更新 right = middle - 1 后继续查找
- 循环退出的条件是 left > right,此时 left 所指向的,就是左边界
- 右边界的查找同理,只是第 4 步的更新操作改为 left = middle + 1,第 5 步中 right 所指向的,就是右边界
你也许会说,找到第一个 target
的时候,然后向左或向右线性搜索不行吗?代码可行,但是这样时间复杂度就成了 O(n + log n),而不是 O(log n)。
我的代码
java
public int[] searchRange(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
return new int[]{getLeftBorder(nums, left, right, target), getRightBorder(nums, left, right, target)};
}
public int getLeftBorder(int[] nums, int left, int right, int target) {
int leftBound = -1; // 记录一下没有找到左边界的时候的情况
while (left <= right) {//退出循环的时候,是right跑到了left左边的时候
int middle = left + (right - left) / 2;
if (nums[middle] == target) {
leftBound = middle; //此时更新leftBound,可以保证最后一次更新的时候,leftBound是我们需要的左边界
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target) {
right = middle - 1;
}
}
return leftBound;
}
public int getRightBorder(int[] nums, int left, int right, int target) {
int rightBound = -1;
while (left <= right) {
int middle = left + (right - left) / 2;
if (nums[middle] == target) {
rightBound = middle;
left = middle + 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target) {
right = middle - 1;
}
}
return rightBound;
}
时间复杂度:O(log n)
空间复杂度:O(1)
这份代码在简洁性上很有大的优化空间,例如可以把寻找左右边界的方法合并,写成一个通用的方法,但拆开写,逻辑其实会更清晰一些。
总结
刚接触二分搜索的时候,不应该上来就想用一个二分法来查找左右边界,很容易把自己绕进去。扎扎实实写两个二分法来分别找左、右边界,逻辑会更加清晰。