从“猜数字游戏”彻底搞懂二分查找

233 阅读3分钟

一、从生活直觉入门:猜数字游戏

先不讲代码,讲一个游戏

你心里想一个 1~100 的数,我来猜。
我每次可以问:“是不是大了 / 小了?”

猜数过程就是二分查找:

回合猜的数结果新区间
150小了[51, 100]
275大了[51, 74]
362小了[63, 74]

每次都排除掉一半的区间。
这就是二分查找的核心思想:缩小搜索空间,直到命中答案。

二、理解“区间”:闭区间 vs 左闭右开

写二分代码最容易出错的,其实是边界。
先搞清楚两种常见区间定义:

闭区间 [left, right]

let left = 0, right = nums.length - 1;
while (left <= right) { ... }
  • 左右端都可能是答案
  • 循环条件要用 <=
  • 每次都在「收缩闭区间」

左闭右开 [left, right)

let left = 0, right = nums.length;
while (left < right) { ... }
  • 包含左端,不包含右端
  • 循环条件是 <
  • 退出时 left === right,表示区间为空

记忆口诀:

  • 「闭区间」用 <=
  • 「左闭右开」用 <
    边界就不会乱!

三、二分查找的三种典型用途

类型目标举例
精确查找找等于 target704. 二分查找
左边界查找找第一个 ≥ target35. 搜索插入位置
右边界查找找最后一个 ≤ target69. x 的平方根

左右边界对称逻辑

查找类型条件判断收缩方向最终返回含义
左边界nums[mid] >= targetright = mid - 1left第一个 ≥ target
右边界nums[mid] <= targetleft = mid + 1right最后一个 ≤ target

记忆口诀:

  • 左边界「卡右边」
  • 右边界「卡左边」
  • 二者镜像互补

四、LeetCode 经典题讲解

704. 二分查找

思路

直接找确切的目标值。
区间采用 闭区间 [left, right]

代码实现

function search(nums, target) {
  let left = 0, right = nums.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (nums[mid] === target) return mid;
    else if (nums[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return -1;
}

34. 在排序数组中查找元素的第一个和最后一个位置

思路

两次二分查找:

  1. 第一次找左边界(第一个 ≥ target 的位置)
  2. 第二次找右边界(最后一个 ≤ target 的位置)

代码实现

function searchRange(nums, target) {
  function findLeft() {
    let l = 0, r = nums.length - 1;
    while (l <= r) {
      const mid = Math.floor((l + r) / 2);
      if (nums[mid] >= target) r = mid - 1;
      else l = mid + 1;
    }
    return l;
  }

  function findRight() {
    let l = 0, r = nums.length - 1;
    while (l <= r) {
      const mid = Math.floor((l + r) / 2);
      if (nums[mid] <= target) l = mid + 1;
      else r = mid - 1;
    }
    return r;
  }

  const left = findLeft();
  const right = findRight();
  return nums[left] === target ? [left, right] : [-1, -1];
}

35. 搜索插入位置

💡 思路

目标:找「第一个 ≥ target 的位置」
左边界查找问题。

代码实现

function searchInsert(nums, target) {
  let left = 0, right = nums.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (nums[mid] >= target) right = mid - 1;
    else left = mid + 1;
  }
  return left;
}

69. x 的平方根 

思路

目标:找「最后一个 ≤ target 的位置 即右边界查找问题。

代码实现

function mySqrt(target) {
  let left = 0, right = target;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (mid * mid <= target) left = mid + 1;
    else right = mid - 1;
  }
  return right;
}

五、什么时候想到用二分法?

判断一个题是否可以用二分,有三大关键特征:

判断问题典型特征示例
1️⃣ 空间有序 / 单调数组有序、数值单调[1,3,5,7]、函数递增
2️⃣ 可以通过 mid 判断方向比大小后知道往左或右收缩“比目标大 → 往左,比目标小 → 往右”
3️⃣ 目标是边界 / 最优解 / 精确值查找、最小值、最大值√x、最小可行速度

只要满足以上三个条件,几乎都能用二分法!