二分与三分查找¶
二分查找是算法竞赛中最为基础且核心的优化技巧,广泛应用于单调性判定问题(二分答案)与区间定位。三分法则常用于单峰/单谷函数的极值求解。
整数二分模版¶
整数二分的关键在于区间定义以及更新方式。推荐掌握以下两种开/闭区间常用模式,彻底杜绝死循环。
模式一:寻找满足条件的第一个位置(左边界 / lower_bound 语义)¶
在区间 \([L, R]\) 内找到满足 check(mid) == true 的最小索引:
int l = 0, r = n - 1;
int ans = -1; // 默认无解
while (l <= r) {
int mid = l + (r - l) / 2;
if (check(mid)) {
ans = mid; // 记录当前可行解
r = mid - 1; // 尝试寻找更小的可行解
} else {
l = mid + 1;
}
}
模式二:寻找满足条件的最右位置(右边界 / upper_bound 语义)¶
在区间 \([L, R]\) 内找到满足 check(mid) == true 的最大索引:
int l = 0, r = n - 1;
int ans = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (check(mid)) {
ans = mid; // 记录当前可行解
l = mid + 1; // 尝试寻找更大的可行解
} else {
r = mid - 1;
}
}
小技巧:使用
l + (r - l) / 2代替(l + r) / 2可以避免由于l + r超过整型最大值而导致的整型溢出。
浮点数二分模版¶
浮点数二分无需考虑整型除法向下取整造成的边界问题,通常有两种控制终止的方式:固定精度 \(\text{eps}\) 或固定循环次数。
推荐在实数二分中直接固定迭代循环 80~100 次。不仅编码极简,且精度可达到 \(2^{-100} \approx 10^{-30}\),彻底避免由于浮点精度抖动导致的死循环。
三分查找模版 (Ternary Search)¶
三分查找适用于在连续区间内寻找严格凸函数(或凹函数/单峰函数)的极值点。
实数域三分¶
设函数 \(f(x)\) 在 \([l, r]\) 上先严格递增后严格递减(求最大值):
double l = -1e9, r = 1e9;
for (int iter = 0; iter < 100; ++iter) {
double m1 = l + (r - l) / 3.0;
double m2 = r - (r - l) / 3.0;
if (f(m1) < f(m2)) {
l = m1; // 极大值位于 [m1, r]
} else {
r = m2; // 极大值位于 [l, m2]
}
}
// 最终极值点约为 l
整数域三分¶
在离散整数区间内三分时,当区间长度较小时直接暴力枚举所有点取最值最稳妥。
ll l = 0, r = 1e12;
while (r - l > 3) {
ll m1 = l + (r - l) / 3;
ll m2 = r - (r - l) / 3;
if (f(m1) < f(m2)) {
l = m1;
} else {
r = m2;
}
}
// 在剩余小区间 [l, r] 内暴力找出极值
ll ans_val = f(l);
ll best_x = l;
for (ll i = l + 1; i <= r; ++i) {
ll cur = f(i);
if (cur > ans_val) {
ans_val = cur;
best_x = i;
}
}
3. LeetCode 经典真题精选与题解提示¶
-
LeetCode 33 - 搜索旋转排序数组 (Search in Rotated Sorted Array)
中等提示
局部单调性二分决策。
- 取中点 \(mid\) 后,整个区间被切分成两半,其中必定有一半是完全单调有序的。
- 通过比较 \(nums[l]\) 与 \(nums[mid]\) 可确定左半部分是否严格有序。在确定有序的一半区间内根据 \(target\) 的范围直接判断下一步缩减方向。
-
LeetCode 34 - 在排序数组中查找元素的第一个和最后一个位置 (Find First and Last Position of Element in Sorted Array)
中等提示
红蓝开闭区间标杆。
- 分别调用两次二分查找:首个 \(\ge target\) 的下标(
lower_bound),以及首个 \(> target\) 的下标(upper_bound)减 1。 - 检查越界与元素相等性即可确定区间 \([first, last]\)。
- 分别调用两次二分查找:首个 \(\ge target\) 的下标(
-
LeetCode 153 - 寻找旋转排序数组中的最小值 (Find Minimum in Rotated Sorted Array)
中等提示
比较基准点收缩。
- 始终以右端点 \(nums[r]\) 作为比较参考系:若 \(nums[mid] < nums[r]\),说明最小值必定落在左半段(含 \(mid\)),令 \(r = mid\);
- 若 \(nums[mid] > nums[r]\),说明最小值必定落在右半段,令 \(l = mid + 1\)。
-
LeetCode 162 - 寻找峰值 (Find Peak Element)
中等提示
局部二分爬坡模型。
- 比较 \(nums[mid]\) 与 \(nums[mid + 1]\):若处于上升坡(\(nums[mid] < nums[mid + 1]\)),则右侧必定存在至少一个峰值,令 \(l = mid + 1\);
- 若处于下降坡,则左侧(含 \(mid\))必定存在峰值,令 \(r = mid\)。严格 \(\mathcal{O}(\log N)\)。
-
LeetCode 410 - 分割数组的最大值 (Split Array Largest Sum)
困难提示
二分答案判定模型标杆。
- 判定函数具备单调性:若子数组各自和的最大值上限为 \(M\),贪心累加当前段,超过 \(M\) 则开启新段,统计所需段数是否 \(\le k\)。
- 二分范围:下界为 \(\max(nums)\),上界为 \(\sum nums\)。
-
LeetCode 875 - 爱吃香蕉的柯珂 (Koko Eating Bananas)
中等提示
最值速度二分答案。
- 速度 \(v\) 越快,吃完所需时间总和单调递减。
- 二分吃香蕉速度 \(v \in [1, \max(piles)]\),单次检验计算 \(\sum \lceil pile / v \rceil \le h\)。