二分查找与二分答案
💡 核心思想
二分查找是在有序序列中快速定位目标元素的算法,时间复杂度为 O(log n)。二分答案是更高级的应用:当问题的答案具有单调性时(即"如果 x 可行,则所有小于 x 的也一定可行"),可以通过二分来寻找最优解,将判定问题转化为验证问题。
二分是竞赛中最常用的技巧之一,也是初学者最容易写错的算法。90% 的二分错误来自边界条件:到底是 `l < r` 还是 `l <= r`?`mid` 应该加 1 还是不加?我的铁律是:始终使用 `l < r` 循环,根据情况选择 `mid = (l + r) >> 1` 或 `mid = (l + r + 1) >> 1`,最终 `l == r` 就是答案。多练几题,手就不会抖了。
🎯 直觉理解
想象你在词典里查一个单词。你不会从第一页开始翻,而是先翻到中间,看目标单词在左半边还是右半边,然后继续折半——这就是二分查找。二分答案则像"猜数字游戏":我猜 50,你告诉我是大了还是小了,我再猜 25 或 75,每次都能排除一半的可能性。
📝 算法流程
- 确定搜索区间 [l, r],确保答案一定在区间内
- 计算中点 mid = (l + r) / 2(或根据情况调整)
- 比较 mid 与目标值:如果 mid 太小,则答案在右半区间 [mid+1, r];如果 mid 太大,则答案在左半区间 [l, mid-1]
- 重复步骤 2-3 直到 l > r(查找)或 l == r(二分答案)
- 二分答案需额外编写 `check(x)` 函数验证可行性
$$T(n) = O(log n) \text{(每次排除一半元素)}$$
$$\text{二分答案框架:while }(l < r)\text{ 取 }mid\text{,若 }check(mid)\text{ 成立则 }r = mid\text{,否则 }l = mid + 1$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(log n)$ 查找 / $O(log V cdot check)$ 二分答案 |
| 空间 | $O(1)$ |
💻 参考实现(C++)
C++ (C++17)
#include
using namespace std;
// 1. 标准二分查找:在有序数组中找 target
int binarySearch(vector& a, int target) {
int l = 0, r = a.size() - 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (a[mid] == target) return mid;
else if (a[mid] < target) l = mid + 1;
else r = mid - 1;
}
return -1; // 未找到
}
// 2. lower_bound:第一个 >= target 的位置
int lowerBound(vector& a, int target) {
int l = 0, r = a.size(); // 注意 r = size()
while (l < r) {
int mid = (l + r) >> 1;
if (a[mid] < target) l = mid + 1;
else r = mid;
}
return l;
}
// 3. 二分答案示例:找出最小的 x 使得 f(x) >= target
bool check(long long x) {
// 验证 x 是否可行
return x * x >= 100; // 示例:找平方 >= 100 的最小数
}
long long binaryAnswer() {
long long l = 0, r = 1e9;
while (l < r) {
long long mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l; // l == r 时就是答案
}
int main() {
vector a = {1, 3, 5, 7, 9, 11, 13};
cout << binarySearch(a, 7) << endl; // 3
cout << lowerBound(a, 6) << endl; // 3 (a[3]=7 是第一个 >=6 的)
cout << binaryAnswer() << endl; // 10
return 0;
} ⚠️ 常见坑点
边界条件写错——`l <= r` 和 `l < r` 的选择取决于是否需要检查最后一个元素
死循环——当 `l = mid` 且 `r = mid + 1` 时,若 `mid = (l + r) >> 1` 可能永远卡在 l
整数溢出——`l + r` 可能溢出,应写为 `l + (r - l) / 2`
二分答案的 check 函数写错——单调性不满足时二分答案失效
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P2249 查找 | 洛谷 | CSP-J | 标准二分查找模板题 |
| P1873 砍树 | 洛谷 | CSP-S | 二分答案经典题:最大化最小值 |
| P2678 跳石头 | 洛谷 | CSP-S | 二分答案 + 贪心验证 |