LeetCode 704. 二分查找:手写一个 O(log n) 的搜索算法

19 阅读3分钟

题目描述

704. 二分查找
难度:⭐ 简单

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

要求:算法时间复杂度为 O(log n)

示例

输出: 4
解释: 9 出现在 nums 中并且下标为 4

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示

  • 数组中的元素不重复
  • 数组长度 n 在 [1, 10000] 之间
  • 每个元素在 [-9999, 9999] 之间

解题思路

题目明确要求 O(log n)  的时间复杂度,看到这个复杂度,几乎立刻就能想到二分查找

二分查找的核心思想是:

  1. 在有序数组中,每次取中间位置的元素与目标值比较。
  2. 如果中间元素等于目标值,则直接返回下标。
  3. 如果中间元素大于目标值,说明目标值在左半部分,缩小右边界。
  4. 如果中间元素小于目标值,说明目标值在右半部分,缩小左边界。
  5. 重复这个过程,直到找到目标值或区间无效。

关键在于区间的定义,是左闭右闭 [left, right] 还是左闭右开 [left, right)
这里我们采用最常用的左闭右闭区间,保证每次搜索都在有效的索引范围内。

边界处理细节

  • 初始化 left = 0right = nums.length - 1(因为右边界是包含的)。

  • 循环条件 while (left <= right):当 left > right 时,说明区间内没有元素,查找失败。

  • 计算中间值:mid = left + Math.floor((right - left) / 2),避免大数溢出(虽然 JS 中不常见,但习惯这样写更安全)。

  • 根据 nums[mid] 与 target 的大小关系调整 left 或 right

    • nums[mid] > target → right = mid - 1
    • nums[mid] < target → left = mid + 1
    • 相等 → 返回 mid
  • 循环结束未找到,返回 -1

代码实现

 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let left = 0;
    let right = nums.length - 1;

    while (left <= right) {
        // 防止 left + right 溢出(虽然 JS 数字范围很大,但这是良好习惯)
        let mid = Math.floor(left + (right - left) / 2);

        if (nums[mid] > target) {
            right = mid - 1;      // 目标在左半边
        } else if (nums[mid] < target) {
            left = mid + 1;       // 目标在右半边
        } else {
            return mid;           // 找到目标
        }
    }

    return -1;                    // 未找到
};

复杂度分析

  • 时间复杂度:O(log n)
    每一次循环都将搜索范围缩小一半,因此最多需要 log₂(n) 次比较。
  • 空间复杂度:O(1)
    只使用了常数个变量,没有额外数组或递归栈。

常见错误与避坑指南

  1. 循环条件写错
    如果使用 while (left < right) 配合闭区间,会漏掉 left == right 时的情况,导致某些边界元素被跳过。
  2. mid 更新写法
    let mid = Math.floor((left + right) / 2) 在 left 和 right 很大时可能有整数溢出风险(其他语言),推荐 left + (right - left) / 2
  3. 边界移动错误
    当 nums[mid] > target 时,应该让 right = mid - 1 而不是 right = mid,否则可能死循环(因为 mid 已被排除)。
  4. 没有处理数组为空的情况
    题目保证了 n >= 1,但实际工程中建议增加 if (!nums.length) return -1

总结

二分查找虽然基础,但它是许多高级算法(如二叉搜索树、快速排序、查找旋转排序数组等)的基石。
只要记住 区间不变量(这里我们保证 [left, right] 始终是待搜索区间),并在更新边界时保持这个不变性,就能写出正确的二分查找。

这道题也提醒我们:简单并不意味着可以掉以轻心,边界条件永远是二分查找的灵魂。


你学会了吗?  不妨自己手动模拟一遍示例的查找过程,或者在本地写几个测试用例加深理解。如果觉得文章有帮助,欢迎点赞、收藏、评论交流~ 😊