这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战
题目
给定一个按照升序排列的整数数组 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]
思路
如果不考虑时间复杂度的问题,可以直接遍历题目所给数组,当 nums[i] 和 目标值 target 相等时,判断是第一次相等,或者是最后一次相等,具体步骤:
-
定义
ans = [-1, -1] -
遍历数组
nums -
如果
nums[i] == target-
如果这时
ans[0] == -1,说明是第一次相等,将ans[0] = i -
如果
i == n -1 || nums[i + 1] > nums[i],说明已经匹配到最后一个和target相等的元素位置,将ans[1] = i
-
-
返回
ans即为题目所求
代码如下
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let ans = [-1,-1];
const n = nums.length;
for(let i = 0; i < n; i++) {
if(nums[i] == target) {
if(ans[0] == -1) {
ans[0] = i;
}
if(i == n - 1 || nums[i + 1] > nums[i]) {
ans[1] = i;
}
}
}
return ans;
};
思考: 上述算法的时间复杂度为 O(n),如果要获得时间复杂度为 O(log n) 的解法,就必然要进行二分了。
上述问题,可以拆解为寻找 第一个等于target的位置 和 第一个大于target的位置减 1
先考虑 第一个等于target的位置,二分查找时,
-
初始时定义两个指针
left = 0,right = n - 1,ans = nums.length -
当
left < right时遍历 (注意坑)-
取中间数
mid = Math.floor((right - left) / 2) + left -
如果
nums[mid] > target,进行right = mid - 1, ans = mid,把区间缩小,把ans的可能值也缩小 -
如果
nums[mid] == target,这时要寻找的是第一个等于target的位置,所以这个时候,依然需要进行right = mid - 1, ans = mid,把区间缩小,把ans的可能值也缩小 -
如果
nums[mid] < targte,进行left = mid + 1
-
上述遍历结束后,ans 应该为最后一个区间的右边界 right,但是,第一个等于target的位置 会不会出现在左边界 left 上呢?答案是可能的,例如在 [1,2,2,2,3] 查找数字 2 时,ans 应该在左边界上。所以上述代码需要多进行一次比较,循环条件需改为 left <= right
再考虑 第一个大于target的位置,二分查找时,
- 如果
nums[mid] == target,这时要查找的是比target大的数,要做的是left = mid + 1
其他都相同
最后,需要校验两次寻得的 ans 是否符合题目所求
代码如下
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let ans = [-1, -1];
const searchIdx = (nums, target, isLast) => {
let n = nums.length;
if(!n) return -1;
let left = 0, right = n - 1, ans = n;
while(left <= right) {
let mid = Math.floor((right - left) / 2) + left;
if(nums[mid] > target || (isLast && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
let leftIdx = searchIdx(nums, target, true);
let rightIdx = searchIdx(nums, target, false) - 1;
if(leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] === target && nums[rightIdx] === target) {
ans = [leftIdx, rightIdx];
}
return ans;
};
小结
二分查找的想法很简单,但是细节需要非常仔细才能想完全,多练才会有正确的思路
LeetCode 👉 HOT 100 👉 在排序数组中查找元素的第一个和最后一个位置 - 中等题 ✅
合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍
如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄