文章目录
一、什么是二分答案?
各位程序员朋友(特别是算法竞赛选手),你们一定遇到过这样的场景:题目要求找出满足条件的最大值/最小值,但直接求解又无从下手。这时候就该祭出我们的二分答案大法了!!!
简单来说,二分答案就是把答案当作搜索目标进行二分查找。和传统二分法最大的区别在于——我们不是直接在数组里找元素,而是在答案的可能范围里找符合条件的最优解。
举个🌰:假设我们要找能举起100kg重物的最细木棍直径。常规做法可能要试遍所有可能直径,但用二分答案只需要logN次验证!
二、二分答案的四大核心要素
1. 答案的单调性(超级重要!!!)
这是二分答案能成立的前提条件。必须满足:当候选答案X满足条件时,所有≥X(或≤X)的值也都满足条件。
比如在"寻找第一个坏版本"问题中,一旦某个版本是坏的,之后所有版本都是坏的——这就是典型的单调性。
2. 确定搜索范围
需要先确定答案的可能区间:
- 最小值:通常是0或1
- 最大值:根据题目约束确定
- 技巧:遇到不确定的情况,可以先估算数据规模
3. 验证函数
这是整个算法的灵魂所在!需要编写一个check(x)函数,判断当前候选答案x是否满足题目条件。
(这里有个坑:很多新手会把验证函数写得特别复杂,其实应该保持简洁,只做条件判断)
4. 边界处理
二分法最让人头疼的就是边界条件。常见的两种写法:
// 左闭右开写法
while(left < right){
int mid = left + (right - left)/2;
if(check(mid)){
right = mid;
}else{
left = mid + 1;
}
}
// 闭区间写法
while(left <= right){
int mid = left + (right - left)/2;
if(check(mid)){
ans = mid;
right = mid - 1;
}else{
left = mid + 1;
}
}
三、五大经典应用场景
1. 最大值最小化问题
“把数组分成k个子数组,求最大子数组和的最小可能值”——这是LeetCode 410的经典题目。
解题思路:
- 最小可能值:数组中的最大元素
- 最大可能值:数组总和
- 验证函数:判断当前mid值能否将数组分成≤k个子数组
2. 最小值最大化问题
“在书架放N本书,每本书有特定页数,求最小书架层高”——这类问题在动态规划中很常见,但用二分答案更高效。
3. 最优分配问题
“给工人分配工作,要求最小化最大工作时间”——2023年Google面试真题。
4. 几何问题
“用半径为r的传感器覆盖所有点”——ACM竞赛常见题型。
5. 机器学习调参
(没想到吧?)在神经网络训练中寻找最佳学习率时,也可以用二分答案的思想!
四、手把手实现二分答案
以LeetCode 875 爱吃香蕉的珂珂为例:
题目要求:
N堆香蕉,每小时最多吃一堆。求能在H小时内吃完的最小吃速K(根/小时)。
实现步骤:
-
确定边界:
- left = 1(至少吃1根)
- right = max(piles)(最多需要吃最大堆的数量)
-
编写验证函数:
def can_finish(piles, H, K):
time = 0
for p in piles:
time += (p + K - 1) // K # 向上取整的骚操作
return time <= H
- 二分模板:
left, right = 1, max(piles)
while left < right:
mid = (left + right) // 2
if can_finish(piles, H, mid):
right = mid
else:
left = mid + 1
return left
五、避坑指南(血泪教训)
1. 整数溢出问题
计算mid时要用left + (right - left)//2
而不是(left+right)//2
,特别是在处理大数时!
2. 死循环陷阱
当left和right相邻时,如果更新条件写错就会无限循环。建议先在纸上模拟边界情况。
3. 浮点数处理
遇到实数范围的二分答案时,要改用精度控制:
eps = 1e-6
while right - left > eps:
mid = (left + right) / 2
# ...
4. 验证函数优化
这是最容易超时的部分!一定要确保验证函数的时间复杂度足够低。比如在分配问题时,贪心算法通常比动态规划更适合。
六、进阶技巧
1. 预处理加速
在验证函数中如果需要重复计算某些值,可以提前预处理。比如在"切割木棍"问题中先排序数组。
2. 二分答案 + BFS/DFS
有些题目需要结合其他算法,比如在"矩阵中找最大可到达高度"问题中,用二分答案确定高度阈值,再用BFS验证可达性。
3. 动态调整边界
遇到特殊条件时可以智能调整搜索范围。比如当发现某个mid值明显不可能时,可以直接跳跃调整。
七、真实案例分享
去年在参加编程竞赛时遇到这样一个题目:
“在二维平面上有N个充电桩,电动汽车续航R公里,求从起点到终点所需的最小充电次数。”
我们的解题过程:
- 将问题转化为:判断充电次数是否≤K次
- 用二分答案搜索最小的K
- 验证函数使用贪心算法:每次尽可能开到最远的可达充电桩
- 处理边界情况:起点/终点没有充电桩
最终这个解法比动态规划方案快了100倍!(当然也熬夜调试了3个小时…)
八、总结
二分答案就像瑞士军刀——看起来简单,但用好了能解决各种疑难杂症。关键要抓住三个要点:
- 确认问题的单调性(这是前提!)
- 合理设置搜索范围
- 写出高效的验证函数
最后送大家一句话:当遇到求极值的问题时,先想想能不能用二分答案——这可能会节省你几个小时的无用功!
(注:本文示例代码主要使用Python,但思路适用于所有编程语言。在实际面试中建议使用面试官指定的语言实现)