今天我们来拆解 LeetCode 热题 100 中的第 35 题「搜索插入位置」,这道题是二分查找的入门经典,也是很多算法面试的高频考点,非常适合用来理解二分查找的核心逻辑与边界处理。
题目描述
给定一个无重复元素的升序数组 nums 和一个目标值 target,要求:
- 若
target存在于数组中,返回其索引; - 若不存在,返回它按升序插入数组后的位置索引;
- 必须实现时间复杂度为 O (log n) 的算法。
示例:
- 输入:
nums = [1,3,5,6], target = 5→ 输出:2(找到目标值,直接返回索引) - 输入:
nums = [1,3,5,6], target = 2→ 输出:1(插入到元素 3 的前面) - 输入:
nums = [1,3,5,6], target = 7→ 输出:4(插入到数组末尾)
解题思路:二分查找的核心逻辑
这道题的本质是在有序数组中找到第一个大于等于 target 的位置 ,也就是算法中常说的 lower_bound 问题。二分查找的优势在于,每次可以将问题规模减半,将时间复杂度从暴力遍历的 O (n) 优化到 O (log n)。
关键细节
-
指针初始化 :左指针
left指向数组开头,右指针right指向数组末尾,形成闭区间[left, right]。 -
中间位置计算 :使用
left + Math.floor((right - left) / 2)而非(left + right) / 2,避免数组长度过大时出现数值溢出问题。 -
区间收缩逻辑:
- 若
nums[mid] == target:直接返回mid,找到目标值; - 若
nums[mid] < target:目标值在右侧区间,更新left = mid + 1; - 若
nums[mid] > target:目标值在左侧区间,更新right = mid - 1。
- 若
-
循环结束处理 :当
left > right时循环结束,此时left就是目标值的插入位置,因为它指向第一个大于target的元素索引。
代码实现(JavaScript)
javascript
运行
ini
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
// 防止溢出,等价于 Math.floor((left + right) / 2)
const mid = Math.floor(left + (right - left) / 2);
if (nums[mid] === target) {
// 找到目标值,直接返回索引
return mid;
} else if (nums[mid] < target) {
// 目标值更大,去右边找
left = mid + 1;
} else {
// 目标值更小,去左边找
right = mid - 1;
}
}
// 没找到,left 就是插入位置
return left;
};
复杂度分析
- 时间复杂度:O (log n),每次循环将问题规模减半,最多循环 log₂n 次;
- 空间复杂度:O (1),仅使用常数级别的额外空间。
常见误区与优化
很多同学写这道题时,容易在循环条件和指针更新上出错:
- 错误地使用
left < right作为循环条件,导致边界处理逻辑复杂; - 中间位置直接写
(left + right) / 2,忽略了数值溢出的风险; - 循环结束后不知道该返回
left还是right,这也是二分查找的经典易错点。
其实只要理解了 "循环结束时 left 一定指向第一个大于等于 target 的位置",这个问题就迎刃而解了。
这道题虽然简单,但二分查找的思想在很多算法题中都有延伸,比如查找重复元素的左右边界、旋转有序数组的最小值等。掌握好这道题,能帮你打下扎实的二分查找基础。