大家好,我是Fairy,一名打完蓝桥杯 C/C++ A 组的正在复盘的大一计科女生。
这几天在疯狂刷题突击期,我终于翻过了一座初学算法时最容易卡壳的大山——二分答案(Binary Search on Answer) 。
以前一看到长篇大论的题目就发憷,但经过大量真题的毒打后,我总结出了一套自己的“二分法肌肉记忆”。今天这篇文章,不讲空洞的理论,只分享我自己在考场上的一套实战解题SOP(标准作业程序) 。如果你也经常在二分法的边界条件里迷失,希望这篇复盘能帮到你。
一、 题眼锁定:什么时候该用二分法?
在刷题时,我给自己定了一个死规矩:只要在题目最后一句的问法中,看到“最大化最小值”或者“最小化最大值”(比如:最多能分多长、最少需要多少时间),不要犹豫,99% 是二分答案。
这种题目的特征非常明显:
- 直接去求答案(正向推导)极度困难,甚至要写极其复杂的 DFS。
- 但是,如果我随便“猜”一个答案,让你去验证这个答案行不行(逆向验证),却非常简单。
- 答案存在单调性(比如:木头切得越长,能分给的人就越少;时间给得越宽裕,越容易完成任务)。
只要满足这三点,直接启动二分法解题流程。
二、 我的解题起手式:先开大数组
面对二分题,我的代码习惯是绝对不在 main 函数里开数组。解题前,我都会在 #include 下方,先把所有需要的变量全部挂在全局区:
#include
#include
using namespace std;
// 1. 无脑开全局大数组,防止栈溢出 // 2. 只要有求和、累加的操作,无脑上 long long long long a[200005]; long long N, M; 全局数组自带初始化为 0 的光环,这在后续写前缀和或者极值判断时,能避开无数个隐蔽的 Bug。
三、 稳住阵脚:先写 while 循环模板
我解题的顺序是倒着来的:先写盲猜答案的 while 模板,再去死磕 check 函数。
外层的 while 循环就像是一个“盲猜员”,它的任务就是在 [left, right] 区间里不断折半,寻找那个极限答案。这里的模板可以直接肌肉记忆:
long long left = 1;
long long right = 1e9; // 根据题目数据范围定,或者取 max_a
long long mid = 0, ans = 0;
while (left <= right) { // 防溢出神仙写法:千万别写 (left+right)/2 mid = left + (right - left) / 2;
if (check(mid)) {
ans = mid; // 记录当前靠谱的答案
left = mid + 1; // 满足条件,尝试进一步压榨极限(视求最大还是最小而定)
} else {
right = mid - 1; // 不满足条件,乖乖退让
}
} cout << ans << endl;
四、 全场核心:最考验逻辑的 check 函数
这也是我写这篇博客最想感叹的一点:整个二分答案的难点,全在 check 函数中! 它是整个代码求解的关键,也是最考验逻辑的点。
如果说外面的 while 是盲猜员,那 check 函数就是铁面无私的**“裁判员”**。它接过 mid(猜的答案),结合题意去模拟一遍流程,最后只返回 true(可行)或 false(不可行)。
在刷题过程中,我发现在 check 函数里最容易犯两个逻辑致命伤,我拿经典的**“分书/切木头”**题型来举例:
避坑 1:变量覆盖 vs 变量累加
假设题目要求每天看书时间不能超过 mid。在遍历数组时,经常会写错累加逻辑:
bool check(long long mid) {
long long today_time = 0;
int days = 1; // 注意:只要做事,天数起步就是 1!
for (int i = 0; i < N; i++) {
if (a[i] > mid) return false; // 单个任务直接爆表,绝无可能
if (today_time + a[i] > mid) {
// 超时了,推到第二天
days++;
today_time = a[i];
} else {
// 没超时,今天继续干!
// 【致命坑点】:这里必须是 +=,绝不能写成 =,否则今天前面的进度全清零了!
today_time += a[i];
}
}
return days <= M; // 最后对比的,是天数有没有超标
} 太棒了!原来你是想写一篇纯粹的硬核技术复盘博客。这种“方法论”加“实战心得”的写法,在掘金上绝对是爆款潜质,因为你精准地切中了无数算法新手的痛点——知道要用二分,但就是写不对。
把你刚才的真实感悟,结合你这几天打磨出来的代码逻辑,我为你重新梳理了一篇技术极客风的掘金博客。这篇博客不仅有你的心路历程,还有极其标准的方法论总结。
你可以直接把下面的 Markdown 文本复制到掘金的编辑器里:
【算法破局】看到“最大的最小”,我直接掏出二分答案模板
你好,我是洵澈,一名正在准备蓝桥杯 C/C++ A 组的大一计科女生。
这几天在疯狂刷题突击期,我终于翻过了一座初学算法时最容易卡壳的大山——二分答案(Binary Search on Answer) 。
以前一看到长篇大论的题目就发憷,但经过大量真题的毒打后,我总结出了一套自己的“二分法肌肉记忆”。今天这篇文章,不讲空洞的理论,只分享我自己在考场上的一套实战解题SOP(标准作业程序) 。如果你也经常在二分法的边界条件里迷失,希望这篇复盘能帮到你。
一、 题眼锁定:什么时候该用二分法?
在刷题时,我给自己定了一个死规矩:只要在题目最后一句的问法中,看到“最大化最小值”或者“最小化最大值”(比如:最多能分多长、最少需要多少时间),不要犹豫,99% 是二分答案。
这种题目的特征非常明显:
- 直接去求答案(正向推导)极度困难,甚至要写极其复杂的 DFS。
- 但是,如果我随便“猜”一个答案,让你去验证这个答案行不行(逆向验证),却非常简单。
- 答案存在单调性(比如:木头切得越长,能分给的人就越少;时间给得越宽裕,越容易完成任务)。
只要满足这三点,直接启动二分法解题流程。
二、 我的解题起手式:先开大数组
面对二分题,我的代码习惯是绝对不在 main 函数里开数组。解题前,我都会在 #include 下方,先把所有需要的变量全部挂在全局区:
C++
#include <iostream>
#include <algorithm>
using namespace std;
// 1. 无脑开全局大数组,防止栈溢出
// 2. 只要有求和、累加的操作,无脑上 long long
long long a[200005];
long long N, M;
全局数组自带初始化为 0 的光环,这在后续写前缀和或者极值判断时,能避开无数个隐蔽的 Bug。
三、 稳住阵脚:先写 while 循环模板
我解题的顺序是倒着来的:先写盲猜答案的 while 模板,再去死磕 check 函数。
外层的 while 循环就像是一个**“盲猜员”**,它的任务就是在 [left, right] 区间里不断折半,寻找那个极限答案。这里的模板可以直接肌肉记忆:
C++
long long left = 1;
long long right = 1e9; // 根据题目数据范围定,或者取 max_a
long long mid = 0, ans = 0;
while (left <= right) {
// 防溢出神仙写法:千万别写 (left+right)/2
mid = left + (right - left) / 2;
if (check(mid)) {
ans = mid; // 记录当前靠谱的答案
left = mid + 1; // 满足条件,尝试进一步压榨极限(视求最大还是最小而定)
} else {
right = mid - 1; // 不满足条件,乖乖退让
}
}
cout << ans << endl;
把这段骨架敲完,整道题的地基就打好了。接下来,迎接真正的挑战。
四、 全场核心:最考验逻辑的 check 函数
这也是我写这篇博客最想感叹的一点:整个二分答案的难点,全在 check 函数中! 它是整个代码求解的关键,也是最考验逻辑的点。
如果说外面的 while 是盲猜员,那 check 函数就是铁面无私的**“裁判员”**。它接过 mid(猜的答案),结合题意去模拟一遍流程,最后只返回 true(可行)或 false(不可行)。
在刷题过程中,我发现在 check 函数里最容易犯两个逻辑致命伤,我拿经典的**“分书/切木头”**题型来举例:
避坑 1:变量覆盖 vs 变量累加
假设题目要求每天看书时间不能超过 mid。在遍历数组时,经常会写错累加逻辑:
C++
bool check(long long mid) {
long long today_time = 0;
int days = 1; // 注意:只要做事,天数起步就是 1!
for (int i = 0; i < N; i++) {
if (a[i] > mid) return false; // 单个任务直接爆表,绝无可能
if (today_time + a[i] > mid) {
// 超时了,推到第二天
days++;
today_time = a[i];
} else {
// 没超时,今天继续干!
// 【致命坑点】:这里必须是 +=,绝不能写成 =,否则今天前面的进度全清零了!
today_time += a[i];
}
}
return days <= M; // 最后对比的,是天数有没有超标
}
避坑 2:判断条件的“边界等号”
在写 check 函数时,必须像强迫症一样审视每一个 > 和 >=。 例如在距离问题中,如果要求工位距离至少为 mid:
// 错误写法:错杀了刚好等于 mid 的合法情况
if (pos[i] - last_pos > mid)
// 正确写法:既然是至少,等于也是合法的! if (pos[i] - last_pos >= mid)
五、 结语
从最初看着 vector 和指针发呆,到现在能熟练地把题目拆解成 二分模板 + 自定义 check 逻辑,这个过程虽然痛苦,但当绿色的 Accepted 亮起时,一切都值了。
总结我的二分法则:
- 看到“最大的最小” -> 启动二分。
- 全局大数组起手 -> 稳住心态。
- 盲敲
while模板 -> 划定边界。 - 全神贯注梳理
check逻辑 -> 抓准>=还是>,厘清是+=还是=。
(大一 CS 女生的算法打怪升级之路,持续更新中... 欢迎大佬们在评论区指正~)