LeetCode 34 在排序数组中查找元素的第一个和最后一个位置的 JavaScript 实现,核心思路是两次二分查找:分别寻找目标元素的左边界和右边界。
算法思路
这道题考察的是二分查找的边界收缩:
- 寻找左边界:当 nums[mid] >= target 时,不立即返回,而是收缩右边界(right = mid - 1),继续向左半部分寻找。
- 寻找右边界:当 nums[mid] <= target 时,收缩左边界(left = mid + 1),继续向右半部分寻找。
注意:由于我们在命中目标时继续收缩边界,最终 left 和 right 会越界(或指向非目标元素)。因此,最后需要判断 left 是否合法,且对应元素是否真的等于 target。
JavaScript 代码实现
/**
@param {number[]} nums
@param {number} target
@return {number[]}
*/
var searchRange = function(nums, target) {
// 寻找左边界
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = Math.floor(left + (right - left) / 2); // 防止整数溢出
if (nums[mid] < target) {
left = mid + 1;
} else {
// 当 nums[mid] >= target 时,收缩右边界
right = mid - 1;
}
}
// 循环结束时,left 指向第一个等于 target 的位置
let leftIdx = left;// 寻找右边界
left = 0;
right = nums.length - 1;
while (left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if (nums[mid] > target) {
right = mid - 1;
} else {
// 当 nums[mid] <= target 时,收缩左边界
left = mid + 1;
}
}
// 循环结束时,right 指向最后一个等于 target 的位置
let rightIdx = right;// 边界检查:如果 leftIdx 越界,或者对应元素不等于 target,说明数组中不存在 target
if (leftIdx < nums.length && nums[leftIdx] === target) {
return [leftIdx, rightIdx];
}return [-1, -1];
};
JavaScript 实现的关键细节
- 防止整数溢出:
虽然 JavaScript 中的 Number 是双精度浮点数,但在处理极大数组时,left + right 依然可能超出安全整数范围。使用 Math.floor(left + (right - left) / 2) 是标准的防溢出写法。 - 搜索区间 [left, right]:
这里使用了左闭右闭区间 [left, right]。- 初始时 right = nums.length - 1。
- 循环条件为 left <= right。
- 这种写法与 Python 版本一致,逻辑直观。
- 为什么最后检查 leftIdx 而不是 rightIdx?
因为 leftIdx 是第一个 >= target 的位置。如果 target 不存在(比如找 9),leftIdx 可能会停在 10 的位置或者越界。只要 leftIdx 合法且 nums[leftIdx] === target,就一定能推导出 rightIdx 也是合法的。 - 时间复杂度:
执行了两次独立的二分查找,时间复杂度为 O(log n),空间复杂度为 O(1)。
掌握这种“遇到目标值不返回,而是继续收缩边界”的二分思想,可以秒杀所有求边界的二分题(如 LeetCode 278 第一个错误的版本、LeetCode 35 搜索插入位置等)。
需要我帮你把这道题的合并写法(单函数实现,通过传入布尔值决定收缩哪一边)也写出来吗?面试时写单函数会显得更精炼。