一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
前言
每天一道算法题,死磕算法
今天继续深入二分搜索,我们来做一下34. 在排序数组中查找元素的第一个和最后一个位置这道题目
题目
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
- 你可以设计并实现时间复杂度为
O(log n)的算法解决此问题吗?
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
分析
题目中说,你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?意思很明白就是让我们使用二分搜索算法
因为有重复值的原因,所以我们不能再使用昨天的那种搜索一个元素的方法
我们一贯秉承着模板大法好的原则,于是先上获取左边界的模板
function leftBound(nums: number[], target: number):number {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = left + Math.floor((right - left) / 2);
// 相等不返回,而是在减小区间
if (nums[mid] === target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
// 查询左边界判断左边界
if (left > nums.length || nums[left] !== target) {
return -1;
}
// 查询左边界返回左
return left;
}
关键点一:
if (nums[mid] === target) {
right = mid - 1;
}
nums[mid] === target的时候,我们不再返回值,而是减小right的值,直到减小right的值在target的左侧的时候,此时由于没有目标值,while最后会结束,由于结束的条件是left===right+1;所以我们返回left就可以拿到左侧的目标值;
关键点二:
if (left >nums.length-1 || nums[left] !== target) {
return -1;
}
这是用来判断没有目标值返回-1的情况,因为我们最后返回的是left;那么我们就可以判断nums[left]!==target的时候,就返回-1,但是有可能left会越界,因为left最小为0,所以我们只需要判断left >nums.length-1 那就是越界了,此时返回-1。
获取右边界的模板和左边界的模板大同小异
function right_bound(nums:number[],target:number):number{
let left = 0;
let right = nums.length-1;
while(left<=right){
let mid = left + Math.floor((right-left)/2);
if (nums[mid] === target) {
left = mid + 1;
}else if(nums[mid]<target){
left = mid + 1;
}else if(nums[mid]>target){
right = mid - 1;
}
}
// 查询右边界看右边界是否越界
if (right < 0 || nums[right] !== target) {
return -1;
}
// 查询右边界返回右
return right;
}
关键点一:
if (nums[mid] === target) {
left = mid + 1;
}
nums[mid] === target的时候,我们不在返回值,而是增大left的值,直到增大到left在target的右侧的时候,此时由于没有目标值,while最后会结束,由于结束的条件是left===right+1;所以我们返回right就可以拿到左侧的目标值;
关键点二:
if (right < 0 || nums[right] !== target) {
return -1;
}
这是用来判断没有目标值返回-1的情况,因为我们最后返回的是right;那么我们就可以判断nums[right]!==target的时候,就返回-1,但是有可能right会越界,因为right最大为nums.length-1,所以我们只需要判断right<0那就是越界了,此时返回-1。
题解
function left_bound(nums: number[], target: number):number {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = left + Math.floor((right - left) / 2);
// 相等不返回,而是在减小区间
if (nums[mid] === target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
// 查询左边界判断左边界
if (left > nums.length-1 || nums[left] !== target) {
return -1;
}
// 查询左边界返回左
return left;
}
function right_bound(nums:number[],target:number):number{
let left = 0;
let right = nums.length-1;
while(left<=right){
let mid = left + Math.floor((right-left)/2);
if (nums[mid] === target) {
left = mid + 1;
}else if(nums[mid]<target){
left = mid + 1;
}else if(nums[mid]>target){
right = mid - 1;
}
}
// 查询右边界看右边界是否越界
if (right < 0 || nums[right] !== target) {
return -1;
}
// 查询右边界返回右
return right;
}
function searchRange(nums: number[], target: number): number[] {
let leftBound = left_bound(nums,target);
let rightBound = right_bound(nums,target);
return [leftBound,rightBound];
};
总结
为了方便记忆,我们依然拿多才多艺的东哥的诗来结束本篇文章
搜索左右区间时,搜索区间要阐明。
if相等别返回,利用mid锁边界。
while结束不算完,因为你还没返回。
索引可能出边界,if检查保平安。