早上打开 LeetCode,习惯性点进 #leetcode# 标签,看到又有人在问"073 爱吃香蕉的狒狒"能不能用二分答案,也有人在复盘周赛430。这画面我太熟悉了。三个月前,我也是从这个标签开始,把热门100题刷了三轮,一路刷到周赛前三题稳定 AC。现在想把这些经验沉淀下来:怎么订计划、怎么写题解、怎么复盘周赛、怎么把一道二分题吃透。这篇文章不教你背题,而是给你一套可复用的刷题项目管理方法,适合被算法面试逼到墙角的人,也适合刷了两百题依然觉得没长进的"题海型选手"。
1. 把 LeetCode 刷成项目:先建目标,再谈题量
1.1 没有反馈循环的刷题都是无效努力
大多数人打开题库就是按难度排序,从 Easy 开始连刷,刷满 100 题后打开面试题还是手抖。我之前也这样,直到有一阵子发现,昨天看过的题解今天就忘了。问题不在记忆力,在于我没有建立反馈循环。
项目管理的核心是目标、执行、反馈、调整,刷题完全可以照搬。目标要具体,比如三个月刷完热门100题,周赛稳定三题;执行要有固定节奏,每天 45 分钟,而不是周末暴刷 6 小时;反馈要靠数据和复盘,比如提交准确率、卡壳位置、重做正确率;调整则是每次周赛结束后修正下一周的方向。没有这套循环,刷题就只是"打卡观光"。
很多人刷题为什么坚持不下来?因为只有"今天又做了一道题"这个即时反馈,没有"我哪一类问题变强了"的累积反馈。等新鲜感一过,剩下的就只有自我感动。而当你把刷题当成一个项目,每一道题都变成项目里的一个里程碑,你会清楚看到自己在前进,还是原地转圈。
1.2 我给项目定的三项指标
给自己定指标时,我选了三项最不容易骗自己的:提交通过率、解题熟练度、重做正确率。
| 指标 | 定义 | 我设定的下限 | 作用 |
|---|---|---|---|
| 提交通过率 | 一次通过次数 / 总提交次数 | 60% | 反映实现的严谨度,防止只靠试错过题 |
| 解题熟练度 | 中等题从读题到有思路的时间 | 20 分钟内 | 模拟面试压力下的反应速度 |
| 重做正确率 | 两周后重做旧题不翻题解的概率 | 80% | 检验是不是真正内化,而不是短期记忆 |
这三个指标互相牵制。如果只追求 AC 数量,很容易刷完就忘;如果只看重一次通过,做题会变得太保守,总是反复检查导致效率极低。我每个指标按周汇总,连续两周不达标就调整策略。
比如第一周我的重做正确率只有 40%,我立刻意识到刷太快、没有及时复盘;后来放慢节奏,每道题做完加一个"两周后重做"的日历提醒,三周后就升到了 75%。这就是反馈循环在起作用。
1.3 一张表治好了我的"盲目刷题"
我用 Notion 建了一张记录表,每次做完一题只花两分钟填写。字段包括:日期、题目编号、标题、难度、标签、首次解题用时、提交次数、卡壳点、是否需复盘、两周后重做。
重点是"卡壳点"这一列,一定要写清楚是"没想到用二分"还是"边界条件漏了",而不是写"太难了"。记录两周后再看,你会发现自己卡壳集中在少数几个模式,这就是下一轮刷题的重点。我自己复盘时发现,80% 的卡壳都发生在边界条件和读题不细上,于是后续每次提交前都强制自己检查数组为空、索引边界和整数溢出。
这张表还能配合标签筛选。比如筛出"二分"标签,看看哪道题提交次数最多、卡壳点最集中,那题就是你需要重新精读的题。数据不会说谎,它比"我觉得我懂了"可靠得多。
2. 热门100题为什么值得刷,以及我的三轮路线
2.1 热门100题不止是"题目数量"
很多人以为热门100题是一个难度清单,其实它是经过大量提交、投票和讨论过滤出来的高频考点集合,价值远超过"刷了 100 个题目"。
它的核心价值在于:每道题都覆盖一个高频考点,而且解法通常不止一种。比如"两数之和"带出哈希补数思想,"合并区间"带出排序加贪心,"无重复字符的最长子串"带出滑动窗口。这些考点标签比题目本身重要,把这些标签掌握后,面试中 80% 的题都能归入已知框架。
更要紧的是,热门100题的题解质量普遍很高。你会发现最高票题解和官方题解往往给出不同思路,对比它们,才能真正理解一种算法的适用边界。只看结论不看比较的人,很难得到这种收获。
2.2 第一轮:按标签扫盲,不用和自己较劲
第一轮的目标不是第一次就 AC,而是知道每个标签下的经典题长什么样。我按数组、哈希表、链表、二叉树、二分、栈队列、动态规划的顺序,每个标签选 5 道中等题,总共不到 40 道,花三周跑完。
具体做法是:每道题先自己试 20 分钟,想不出来直接看题解。注意,看完题解必须关掉它,自己重新写一遍。如果当天没写出来,第二天必须再写一次。这一轮会帮你建立一种粗糙直觉:看到二分题知道有 left、right、check 三件套,看到树相关的题知道要递归。
这里有个心理建设:第一轮看不懂题解很正常,别觉得自己笨。我在刷"接雨水"的时候,看了三遍题解还是一头雾水,只好把它先放下,两周后再回来,突然就懂了。有时候不是理解力不行,只是前置知识还没到位。
2.3 第二轮:按频率精刷,看题解要看不同版本
第二轮开始用 LeetCode 的高频题单,同时把第一轮标记"需复盘"的题混进来。这个阶段做题要求升级:必须写注释,记录我当时判断算法的依据。
题解只挑最高票的和官方版看,因为两个人的思路往往不同。比如同一道动态规划题,一个用自顶向下记忆化,一个用自底向上数组,两者对照才能理解状态转移的本质。看完后把自己的解法写在笔记里,核心是写"为什么这个状态定义能避免重复计算",而不是把代码抄一遍。
这个阶段我还做了一件事:把每道题的核心思路压缩成一句口诀。比如"看到最小化最大值,想二分答案","看到所有组合,想回溯","看到层次遍历,想队列"。这些口诀虽然粗糙,但在限时做题时特别管用。
2.4 第三轮:只写思路不写码,把假熟练揪出来
第三轮的核心是防止"看着会做,合上不会做"。我每天随机从热门100题里抽 5 题,只写解题流程:用什么数据结构、复杂度多少、边界怎么处理、核心步骤三行,不写完整代码。
然后和收藏的题解比对。如果思路对,就过;如果漏了边界,标为待重做;如果连用什么算法都没想起来,立刻放入"下周重点"。这一轮很残酷,但非常高效,它能把真实掌握程度从"我记得见过这题"里区分出来。
我第三轮第一次随机抽题时,有 30% 的题连算法类别都想不起来。那一刻我才意识到,前两轮的"会做"有很大一部分是短期记忆。坚持第三轮之后,这个数字降到了 10% 以下。这也是为什么我后来敢参加周赛,因为我的知识已经不再依赖"刚看过"。
3. 用 073 爱吃香蕉的狒狒 讲透"二分答案"
3.1 题目讲的是什么,为什么暴力法会超时
073 爱吃香蕉的狒狒(LeetCode 题号 875)是二分答案题型的代表作。题目给你一堆香蕉 piles,每个元素是一堆的数量。狒狒每小时最多吃 speed 根,但只能在一堆上吃,吃不完宁可等下一小时,也不会把速度用到下一堆。给你 h 小时,求能吃完所有香蕉的最小速度 speed。
如果从 1 开始逐个试到 max(piles),每次检查需要 O(n) 时间,总复杂度是 O(max(piles) * n)。当 piles 长度和单堆数量达到 10^4 和 10^9 级别时,这个计算量直接爆炸。实际问题往往不是求一个符号表达,而是要找一个最小可行值,这种结构非常适合二分答案。
我最初做这道题也走了暴力扫描的路,结果提交后超时,才意识到需要换思路。很多人都卡在这一步,因为他们被"最小速度"里的"最小"骗了,以为必须要从最小那个值一个不漏地试上去。
3.2 单调性分析:为什么可以二分
让 f(speed) 表示以这个速度能否在 h 小时内吃完。speed 越大,耗时越短,所以 f 的取值会从一段 false 开始,某处变成 true,之后一直是 true。我们要找的正是第一个 true 的位置。
这就像在一排开关里找第一个亮灯的位置。既然 false 和 true 的分布是单调的,就能用二分把搜索从线性降到对数级别。拿到任何题想用二分答案时,先问自己一句:如果答案合法,比它更大的答案一定合法吗?如果不单调,就不能二分。
这道题的单调性非常直观:速度从 10 提到 11,吃完全部香蕉只会更快,不会更慢。所以 f(speed) 只可能从 false 变成 true。这就是二分答案能成立的根。
3.3 Python 题解与每一步解释
看代码:
class Solution: def minEatingSpeed(self, piles: List[int], h: int) -> int: def can_eat(speed: int) -> bool: hours = 0 for p in piles: hours += (p + speed - 1) // speed if hours > h: return False return True lo, hi = 1, max(piles) while lo < hi: mid = (lo + hi) // 2 if can_eat(mid): hi = mid else: lo = mid + 1 return lo(p + speed - 1) // speed是上取整写法,等于ceil(p / speed)。为什么不用math.ceil?因为整数运算更快,也没有浮点误差。这个上取整技巧在贪心、模拟类题目里出现频率非常高。
二分部分用的是"找左边界"模板:while lo < hi表示区间只剩一个元素时停止。因为我们要找最小可行速度,所以当can_eat(mid)为真时,答案可能是 mid,也可能更小,于是把上界压到 mid;为假时说明速度不够,答案一定比 mid 大,所以下界设为 mid + 1。
边界条件也很清楚:下界不能是 0,因为速度为 0 没有意义且会死循环;上界取max(piles),因为这个速度下每堆最多一小时吃完,必然能在 h 小时内完成。
3.4 这类题最容易踩的三个坑
第一个坑是上取整写错。有人用round(p / speed),这是完全错误的四舍五入,会导致总时间偏小。正确的整数上取整就是(p + speed - 1) // speed。
第二个坑是二分模板混用。while lo < hi配lo = mid + 1,因为 mid 已经排除掉了;如果你换成while lo <= hi,更新逻辑和返回条件全都得改。很多死循环就是模板记混造成的。
第三个坑是忽略题目保证的h >= len(piles)。这个条件保证至少还有解,但如果你只看样例,很容易漏掉"每小时最多吃一堆"这个约束。周赛里的变式题常常在这个点上做文章,所以平时就要把它写进笔记。
把这道题吃透后,我顺便把"在 D 天内送达包裹的能力""制作花束的最少天数"都按同样模板刷了一遍,效果非常好。二分答案一旦建立了模板,就是一个性价比极高的投入。
4. 周赛430复盘:从两题选手到三题选手的真实路线
4.1 周赛430那天的流水账
周赛430我没有打出多好的成绩,但它给了我一个很关键的提醒:平时刷题和限时做题是完全两种状态。
那场第一题我 4 分钟 AC,第二题却因为没看清"可以重复选择"这个关键词,读题就花了 6 分钟,思路也跟着走偏。第三题明明想到了用二分答案,却因为 check 函数里没有写好提前终止,连续提交两次 WA。赛后看讨论区才发现,第三题的坑就是累计时间一旦超过 h 应该立刻返回 False,而不是继续累加。那一刻我很想抽自己:这个提前终止在 073 爱吃香蕉的狒狒里不是已经写过吗?
为什么平时会写,周赛就忘了?因为平时有时间慢慢磨,周赛一紧张就回到了最本能的写法。所以周赛复盘的意义,就是把这些"本能错误"逮出来,一个个改成新习惯。
4.2 赛后复盘我必问自己的四个问题
现在每次周赛结束,我都会问自己四个问题,逐个写下来:
第一,第一题是不是稳定在 5 分钟内 AC?如果做不到,说明基础 API 和输入输出处理还不够熟。这不需要刷难题,多做做签到题就能练出来。
第二,第二题卡壳的原因是读题还是实现?如果是读题,说明我复盘时的读题习惯不够好。后来我要求自己先圈数据范围和关键词,再开始想算法,效果立竿见影。
第三,看到题目有没有第一时间反应出算法?比如"最小值""时限""连续"这些词,应该直接触发二分答案或滑动窗口的条件反射。如果赛后翻题解发现自己绕了远路,就把那题的关键词记下来。
第四,有没有因为缺少边界检查多交好几发?我统计过,每场周赛 WA 的原因,超过一半是数组空、索引越界、整数溢出。与其交完等报错,不如提交前花 30 秒自查。
这四个问题看起来很基础,但坚持十场之后,我的第三题完成率从 20% 涨到了 70%。靠的不是突然变聪明,而是把每个坑都提前踩过一遍。
4.3 我把周赛时间切成四段
限时比赛最忌讳"一道题卡到底"。我给自己的时间分配很固定:
| 时间 | 目标 | 原则 |
|---|---|---|
| 0-5 分钟 | 完成第一题 | 不追求优雅解法,先 AC 拿分 |
| 5-25 分钟 | 完成第二题 | 读题三遍,先圈约束条件 |
| 25-55 分钟 | 攻第三题 | 15 分钟无明确思路就跳到第四题 |
| 最后 5 分钟 | 边界检查 | 空数组、索引、溢出,统一自查 |
这个分配看起来简单,但能防止你为了第三题纠结 40 分钟,最后连第二题都没做对。周赛的目标首先是稳定拿分,其次才是挑战难题。排名越往上涨,越能感觉到"拿满稳稳能拿的分"比"硬拼不会做的题"划算得多。
5. 我踩过的坑和最后想说的
5.1 最毁节奏的三种心态
第一是只刷数量不看质量。我见过刷了 300 题的人,问他"最长递增子序列"只会套模板,换个包装就认不出来。刷题数量是结果,不是目标。第三轮之后我反而把高频题的刷题次数降下来了,专门去重做旧题。
第二是一道题死磕超过 90 分钟。死磕的边际收益很低,尤其入门阶段,很多题目依赖的套路你根本没学过,靠自己硬想等于闭卷考高数。我的约定是:一道题最多 45 分钟,45 分钟没思路就投降看题解,但 24 小时内必须不看题解重写一次。这样既保证了独立思考,又不浪费时间。
第三是复制粘贴题解后假装"我懂了"。复制代码是最严重的自欺欺人,它给大脑一个"我会了"的错觉,实际上记忆停留不了一周。我每次看完题解都会至少隔半小时再自己写一遍,写不出就继续看,直到能独立提交通过。
5.2 常用高频模板清单
刷到后期,我把高频套路整理成一张清单,做题时第一时间对照:
| 算法 | 出现线索 | 核心要点 |
|---|---|---|
| 二分答案 | 最小化最大值 / 最小速度 / 最短天数 | while lo < hi,check 函数保证单调 |
| 回溯 | 所有组合、排列、子集 | 先画选择树,再写剪枝 |
| DFS/BFS | 岛屿数量、矩阵连通性 | visited 数组避免重复访问 |
| 单调栈 | 下一个更大/更小元素 | 栈内保持单调,出栈时结算答案 |
别把模板当成死代码背,要背的是"看到这类线索后的第一反应"。我每次做题都会问自己:这题如果我想套一个模板,最像哪个?这个问题本身,就是在训练考点归类的速度。
5.3 如果只能给一条建议
要我选出一条最有用的建议,我会选:每道做过的题,两周后无条件重做一次。你可能会觉得这是浪费时间,但它恰恰是防止"短期记忆假象"的唯一可靠手段。
第三轮刷题我随机抽题重做时,准确率只有 70%,意味着 30% 的题我此前根本没掌握。之后我每天固定抽 5 道两周前的题重做,就这样把重做正确率拉到了 90% 以上。这个习惯不需要任何工具,不需要买课,只需要你承认自己记性没那么好,并且愿意每周花一点时间对抗遗忘。
如果你也准备开始刷 LeetCode,别急着把 100 道题塞进一个月。先按我上面的方法建一张记录表,从热门100题里选 10 道中等难度题跑两周;再把 073 爱吃香蕉的狒狒这类二分答案题彻底吃透;第三周去参加一次周赛,回来用那四个问题复盘。我不是什么竞赛选手,三个月前连两数之和都要想十分钟,但靠着这套把刷题当项目的方法,现在已经能在周赛430里稳定做完前三题。先把这件事跑通,你会比那些每天刷五道新题的人走得更远。