题目
给你一个按照非递减顺序排列的整数数组
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]
解答
核心思想
-
使用二分查找的变种:不是直接找目标值,而是找目标值的"边界"
-
两次查找:
-
第一次找目标值的起始位置(第一个≥target的位置)
-
第二次找目标值结束位置(第一个>target的位置-1)
-
算法思路
1. 修改版的二分查找 (代码中binarySearch函数
)
这个版本的二分查找不是直接找目标值,而是找第一个不小于(≥)目标值的位置。这个位置有以下含义:
-
如果目标值存在,它就是目标值的第一个出现位置
-
如果目标值不存在,它就是第一个比目标值大的元素的位置
关键点:
-
当
nums[mid] == target
时,我们继续向左搜索 (right = mid - 1
),因为我们要找的是第一个出现的位置 -
最终
left
指针会停在第一个不小于目标值的位置
2. 搜索范围的主逻辑 (代码中searchRange函数
)
-
找起始位置:
-
直接用
binarySearch(nums, target)
找到第一个不小于target
的位置 -
检查这个位置是否真的等于
target
:-
如果
start
超出数组范围或nums[start] != target
,说明target
不存在
-
-
-
找结束位置:
-
找
target + 1
的插入位置,然后减1 -
例如,对于
[5,7,7,8,8,10]
和target=8
:-
binarySearch(nums, 9)
返回5(第一个不小于9的位置) -
减1得到4,这就是最后一个8的位置
-
-
var binarySearch = function (nums, target) {
let left = 0
let right = nums.length - 1
while (left <= right) {
let mid = Math.floor((left + right) / 2) // 计算中间位置
if (nums[mid] < target) {
left = mid + 1 // 目标值在右半部分
} else {
right = mid - 1 // 目标值在左半部分或当前位置
}
}
return left // 返回第一个不小于目标值的位置
}
var searchRange = function (nums, target) {
// 找到第一个不小于target的位置(即target的起始位置)
const start = binarySearch(nums, target)
// 检查target是否存在于数组中
if (start === nums.length || nums[start] !== target) {
return [-1, -1] // target不存在
}
// 找到第一个不小于target+1的位置,减1得到target的结束位置
const end = binarySearch(nums, target + 1) - 1
return [start, end]
}