力扣34-在排序数组中查找元素的第一个和最后一个位置[二分法]
· 阅读需 5 分钟
原题
给你一个按照非递减顺序排列的整数数组 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] :::tip
提示
-
0 <= nums.length <= 10⁵
-
-10⁹ <= nums[i] <= 10⁹
-
nums 是一个非递减数组
-
-10⁹ <= target <= 10⁹
解题思路
已知:
- 数组有序 - 定位
二分法
- nums
- target 值
- 有可能有重复元素
求:
- target 出现的第一个出现的位置
- target 出现的最后一个出现位置
寻找最左:
- 找到 mid 符合 target 之后,更新 LBoder 为 mid
- 将 r 移动到 mid 左边,寻找更左侧的 target
- 有就继续收缩,没有直接返回 -1
寻找最右:
- 找到 mid 符合 target 之后,更新 RBoder 为 mid
- 将 l 移动到 mid 右边,寻找更右侧的 target
- 有就继续收缩,没有直接返回 -1
明确隐藏条件:
- 若找不到 first,那么 last 也是不存在,因此首次判断 first 存在与否再决定是否寻找 last
- 当能找到 first 时,last 最小也只能是 first,不会出现 -1
- 检查空数组,空数组直接返回 [-1, -1]
解题过程
- 初始化左右指针:left = 0, right = len(nums) - 1
- 二分查找第一个位置
- 如果找到匹配的 target,将 right 指针移到中点的左侧继续查找,直到锁定第一个出现的 target。
- 二分查找最后一个位置
- 如果找到匹配的 target,将 left 指针移到中点的右侧继续查找,直到锁定最后一个出现的 target。
- 返回结果:如果没有找到目标值,返回 [-1, -1]
复杂度
-
时间复杂度: O(logN)
-
空间复杂度: O(1)
代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function (nums, target) {
// 直接检查空数组
const n = nums.length
if (n === 0) return [-1, -1]
function getFirst(nums, target, n) {
// [l, r]
let l = 0
let r = n - 1
let first = -1
while (l <= r) {
const mid = (l + r) >>> 1
// 找到 target 位置后更新 first 位置,收缩右边界,继续向mid左边寻找是否有更左值
if (target === nums[mid]) {
first = mid
r = mid - 1
} else if (target > nums[mid]) {
l = mid + 1
} else {
r = mid - 1
}
}
return first
}
function getLast(nums, target, n) {
// [l, r]
let l = 0
let r = n - 1
let last = -1
while (l <= r) {
const mid = (l + r) >>> 1
// 找到 target 位置后更新 last 位置,收缩左边界,继续向mid右边寻找是否有更右值
if (target === nums[mid]) {
last = mid
l = mid + 1
} else if (target > nums[mid]) {
l = mid + 1
} else {
r = mid - 1
}
}
return last
}
let first = getFirst(nums, target, n)
// 如果找不到第一个位置,说明数组中没有这个元素,那么最后一个位置也是找不到的
// 循环条件上理解则是 一旦 left 为 -1了,在循环条件 left <= right这个上, right也只能是 -1 了
if (first === -1) return [-1, -1]
// 一旦能够找到第一个位置,那么 last 最小也只能是 first
// 循环条件上理解则是 一旦 left > 0 了,在循环条件 left <= right这个上, right也只能是 > 0 了
let last = getLast(nums, target, n)
return [first, last]
}