二分查找:原理、实现、变种与避坑指南

104 阅读4分钟

“在有序的世界里,二分查找是最快的猎手。”

在算法面试和工程实践中,二分查找(Binary Search) 是最基础也最重要的搜索算法之一。它以 O(log n) 的时间复杂度,在有序数据中高效定位目标,远胜线性查找的 O(n)。但看似简单的代码背后,却藏着无数边界陷阱。本文将带你彻底掌握二分查找的核心思想、多种写法、常见变种及避坑技巧。


一、什么是二分查找?

二分查找是一种在有序数组中查找特定元素的高效算法。其基本思想是:

每次将搜索区间一分为二,通过比较中间元素与目标值的大小,决定继续在左半部分还是右半部分查找,直到找到目标或区间为空。

🌰 举个例子:

在升序数组 [1, 3, 5, 7, 9, 11] 中查找 7

步骤leftrightmidnums[mid]操作
105255 < 7 → 查右半
235499 > 7 → 查左半
33337找到!

仅用 3 次比较就完成了查找(而线性查找最多需 6 次)。


二、使用前提

✅ 二分查找必须满足以下条件

  1. 数据结构支持随机访问(如数组,不能是链表);
  2. 元素必须有序(升序或降序,默认讨论升序);
  3. 若存在重复元素,需明确查找目标(任意位置?最左?最右?)。

❌ 在无序数组上使用二分查找,结果不可预测!


三、基础实现:两种主流写法

方法一:闭区间写法(推荐 ✅)

function binarySearch(nums, target) {
    let left = 0, right = nums.length - 1; // [left, right]
    while (left <= right) {
        const mid = left + Math.floor((right - left) / 2); // 防溢出
        if (nums[mid] === target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1; // 未找到
}
  • 循环条件left <= right(区间非空)
  • 优点:直观、易理解,适用于大多数场景

方法二:开区间写法(更安全,适合变种)

function lowerBound(nums, target) {
    let left = -1, right = nums.length; // (left, right)
    while (left + 1 < right) {
        const mid = Math.floor((left + right) / 2);
        if (nums[mid] >= target) {
            right = mid;
        } else {
            left = mid;
        }
    }
    return right; // 第一个 >= target 的下标
}
  • 不变量

    • nums[left] < target
    • nums[right] >= target
  • 优势:天然避免死循环,特别适合处理边界问题(如找左/右边界)


四、常见变种(高频面试题)

普通二分只能找“任意一个目标”,但在实际问题中,我们常需要:

变种功能应用场景
左边界(Lower Bound)返回第一个 ≥ target 的下标找第一个出现的位置
右边界(Upper Bound)返回第一个 > target 的下标找最后一个出现的位置
搜索插入位置若不存在,返回应插入的位置LeetCode 35
二分答案在答案空间中二分,验证可行性求最小最大值、最大最小值等

📌 实战:查找元素的第一个和最后一个位置(LeetCode 34)

var searchRange = function(nums, target) {
    const start = lowerBound(nums, target);
    if (start === nums.length || nums[start] !== target) {
        return [-1, -1];
    }
    const end = lowerBound(nums, target + 1) - 1;
    return [start, end];
};

利用两次 lowerBound,优雅解决重复元素问题!


五、常见误区与避坑指南

问题原因解决方案
死循环更新边界时区间未缩小(如 left = mid确保每次更新后区间严格缩小
整数溢出(left + right) / 2 在大数时溢出改用 left + (right - left) / 2
漏查元素循环条件写成 left < right使用 left <= right 或明确不变量
忽略重复元素普通二分返回任意匹配位置使用左/右边界变种

💡 口诀
左加一,右减一;开闭区间要统一;不同元素看 Map,边界条件多测试。


六、为什么不用三分、四分查找?

虽然直觉上“多分”可能更快,但数学证明表明:

二分查找是最优的
对于 n 个元素,k 分查找的时间复杂度为 O(k·logₖn),当 k=2 时取得最小值。


七、推荐练习题(LeetCode)

题号题目类型
704二分查找基础
35搜索插入位置边界处理
34查找第一个和最后一个位置左右边界
69x 的平方根二分答案
875爱吃香蕉的珂珂二分答案应用

八、总结

  • 二分查找是有序结构中的利器,时间复杂度 O(log n)。
  • 掌握闭区间 vs 开区间两种写法,灵活应对不同场景。
  • 面对重复元素、边界问题时,优先考虑左/右边界变种
  • 细节决定成败:防溢出、循环条件、更新逻辑缺一不可。

建议:不要死记模板,而是理解“二段性”——只要一个问题能被划分为“满足条件”和“不满足条件”两部分,就可以尝试二分!