一、从生活直觉入门:猜数字游戏
先不讲代码,讲一个游戏
你心里想一个 1~100 的数,我来猜。
我每次可以问:“是不是大了 / 小了?”
猜数过程就是二分查找:
| 回合 | 猜的数 | 结果 | 新区间 |
|---|---|---|---|
| 1 | 50 | 小了 | [51, 100] |
| 2 | 75 | 大了 | [51, 74] |
| 3 | 62 | 小了 | [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,表示区间为空
记忆口诀:
- 「闭区间」用
<=- 「左闭右开」用
<
边界就不会乱!
三、二分查找的三种典型用途
| 类型 | 目标 | 举例 |
|---|---|---|
| 精确查找 | 找等于 target | 704. 二分查找 |
| 左边界查找 | 找第一个 ≥ target | 35. 搜索插入位置 |
| 右边界查找 | 找最后一个 ≤ target | 69. x 的平方根 |
左右边界对称逻辑
| 查找类型 | 条件判断 | 收缩方向 | 最终返回 | 含义 |
|---|---|---|---|---|
| 左边界 | nums[mid] >= target | right = mid - 1 | left | 第一个 ≥ target |
| 右边界 | nums[mid] <= target | left = mid + 1 | right | 最后一个 ≤ 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. 在排序数组中查找元素的第一个和最后一个位置
思路
两次二分查找:
- 第一次找左边界(第一个 ≥ target 的位置)
- 第二次找右边界(最后一个 ≤ 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、最小可行速度 |
只要满足以上三个条件,几乎都能用二分法!