
很多人准备算法面试的时候容易陷入一个误区把“刷题数量”当成安全感来源。一天五题、三百题通关听起来很充实但真到了面试现场换个问法、换个包装照样卡壳。我经历过这种阶段也花了不少时间才走出来最后靠的不是更拼命刷题而是一个看起来笨、但后劲很强的方法——建了一个算法刷题本每道题都整理成“题目重述 思路讲解 代码实现”。这篇文章就把这个体系从头到尾拆开讲为什么有效、每道题该记哪些内容、目录怎么搭、讲解怎么写、代码模板怎么沉淀以及一套能滚动起来的复习闭环。1. 为什么刷题本值得做以及它和“刷题量”的关系我最早开始准备算法笔试面试时也给自己定过类似的 KPI一天五题周末十题。刷完之后回头看当天做的题居然有不少已经想不起解法了。当时觉得是自己记忆力差后来才想明白问题不只是记忆而是根本没把做题过程变成一段能复习的内容。算法学习和任何技能学习一样输入很重要但编码和检索同样重要。光做题不整理等于每次都从零开始。这里说的“刷题本”不是把题目抄一遍的错题集而是围绕每道题整理出的三件套题目重述、思路讲解、代码实现。听起来朴实但坚持写一个月之后效果会变得非常明显面试或考试前我不再需要把题库从头翻一遍只需要翻自己的刷题本很快就能把知识框架拉起来。对于备战算法面试、参加竞赛训练、或者想系统学数据结构的同学来说这可能是投入产出比最高的学习动作之一。1.1 刷题的本质其实是模式匹配我越来越理解面试里出算法题考核的不是你会不会背这道题的答案而是你有没有形成一套“读题→翻译约束→匹配数据结构和算法范式”的反射。换句话说刷题的本质是模式匹配看到一道题先判断它是数组题还是链表题是求最值还是计数是无序还是有序然后快速圈定可能的解法范围。这套反射建立得越快解题就越稳。但如果只刷题不整理这类匹配能力非常难形成。原因很简单人的记忆天然是碎片化的今天学二分明天学图后天学动态规划四散在脑海里形不成体系。而刷题本强制你在每道题后面写下“这题属于哪种范式、为什么可以用这种方法”这件事本身就是在给大脑建立索引。没有索引记再多也调取不出来。1.2 不消化的刷题等于做过就忘还有一个很现实的教训多数人刷完题之后不会再看第二次。再好看的重点标注如果只在第一遍起作用那和没有标注没区别。我最早做笔记时写完就再也没翻过。直到有次复习时发现自己一个周前整理过的二分模板已经忘干净了才真正意识到刷题本的核心不是“写”而是“写完还要用”。于是我把“整理”和“复习”绑定在一起每道题写完至少安排一次主动回忆。主动回忆的意思是遮住代码和讲解只看题目重述尝试重新推理。这个步骤很痛苦但恰恰是因为痛苦记忆才被强化。刷题本此时就变成了“检索练习”的工具而不只是一份存档。说到检索练习还有一个反直觉的点整理一条题目花的时间并不比多刷一道新题多多少但前者是复利增长后者是单次消耗。同一套知识体系整理后再复习两次掌握度远胜于闷头刷十道新题。2. 知道一道题该记什么往往比“怎么记”更重要很多人一开始都会犯一个错以为记录就是把代码存下来。我刚开始也是这样把通过的代码一贴再写个“这题不难”就以为整理完了。结果两天后再看完全看不懂自己写了什么。后来我才意识到代码只是知识的外壳思路和推演过程才是真正需要记下来的内核。刷题本的价值不在于存档而在于让未来的自己能在最短时间内重新理解这道题。2.1 一道题记录的六个基本模块现在我的每条记录固定有六个模块题目重述、输入输出样例、思路讲解、代码实现、复杂度分析、易错点。用表格解释一下模块具体写什么为什么需要题目重述用自己的话重写题目强调关键限制检验是否真正理解题意输入输出样例1-2 组典型样例 1 组边界样例调试和验证的基准思路讲解从暴力到最优的推导步骤复习时的主线代码实现精简后的核心代码去掉无关 IO直接可运行、可对比复杂度分析时间、空间复杂度及额外空间来源判断是否是最优解易错点踩过的小坑、边界条件二刷时快速唤醒写这六个模块时我要求自己做到“一周后能靠这份记录重新讲题”不追求完美排版。每个模块可以很短但必须存在。缺失任何一个都会让这条记录在某个复习节点上“卡壳”。比如缺少边界样例复习时还得自己重新推演一遍缺少易错点记录之前踩过的坑大概率会再踩一次。2.2 用一个真实条目演示两数之和我拿经典的“两数之和”来做示范。题目重述我一般这样写给定一个整数数组 nums 和一个目标整数 target返回两个下标这两个下标对应数字的和等于 target。默认存在唯一答案且不能重复使用同一元素。注意我会把“唯一答案”和“不能重复使用同一元素”这两个隐含条件写进重述里。这两个条件决定了实现中要不要处理重复值冲突以及要不要考虑顺序问题。很多人抄题时忽略这些隐含条件恰恰是复习时理解代码逻辑的最大障碍。思路讲解部分我会先写暴力两层循环枚举 i 和 j判断 nums[i] nums[j] target复杂度 O(n²)。然后写优化动机内层循环是在重复查找 target - nums[i] 是否存在而“查找”正是哈希表最擅长的事。再进一步写实现细节遍历到每个数时先查哈希表里有没有 target - nums[i]没有就把当前数存进哈希表继续遍历。这样记录下来的每一步都有“为什么”在支撑。代码实现部分我会拆成最小可运行片段注释只标关键点不贴大段无关逻辑。这样复习时可以直接复现核心思路而不是在一堆 IO 代码里找重点。2.3 原题太长时记录也要讲究“抓变量”有些题目题干很长比如模拟类问题一次输出来一堆操作。这种题我不建议把题干原样抄进去而是提炼出三个变量输入是什么、限制条件是什么、输出要求是什么。真正在复习时需要的是这几个变量而不是对故事场景的复述。这种化繁为简的处理靠的就是一遍遍写题目重述慢慢练出来的能力。3. 刷题本的分层索引数据结构、范式标签与难度单条记录写再多如果没有目录资料依旧不好用。刷题本到后期题量会很大几百条记录如果只是按时间顺序堆着想找一道题都费劲。我采用的是三级索引结构章节目录、范式标签、难度标签。三套维度各管一件事互相补充。3.1 第一层按数据结构分章节我的刷题本目录第一层是数组与字符串、哈希表、链表、栈与队列、树、图、动态规划、贪心、回溯、排序与搜索、位运算与数学。每个章节下再按具体题型细分。之所以按数据结构分而不是按难度分是因为人遇到新题时的第一反应通常是“这道题发生在什么数据结构上”而不是“它是简单题还是难题”。按数据结构分章目录本身就在暗示思考方向。这种做法还有一层隐性好处复习到某个数据结构时可以在一章内一口气过十几种题形成同类记忆的密集冲击。比如链表章节里集中看反转类、环检测类、合并类章内题材的重复度会极大促进模式记忆。同样是复习二十道题分散翻和集中翻效果差别很大。3.2 第二层用范式标签交叉引用光有章节还不够因为一道题可能同时属于“数组 哈希表 双指针”三个范畴。我的做法是给每条记录打范式标签比如双指针、滑动窗口、前缀和、单调栈、分治、DFS/BFS、动态规划、并查集、拓扑排序等。标签不参与目录排序只负责交叉检索。想复习滑动窗口时直接按标签过滤一遍所有分散在不同章节的滑动窗口题就被拉出来一起对比。题目示例章节范式标签难度例 1数组与字符串双指针、排序T2例 2数组与字符串滑动窗口、哈希T3例 3链表快慢指针T2例 4树DFS、递归T3这种标签设计还有一层好处当一道新题做不出来时我会回头翻同标签的老题经常会有一种“原来这个解法在那个章节见过”的顿悟。刷题本随后就从记录工具变成了解题时的灵感库这是光抄题解永远得不到的。3.3 难度标签用 T0-T4跟平台难度脱钩平台难度是给大众参考的跟每个人的掌握程度不一定匹配。所以我的难度标签完全由自己定义T0 是不用想直接秒的题T1 是能稳定在二十分钟内解决T2 是中等难度需要点推导T3 是卡过一次以上、必须多轮复习的题T4 是当前阶段的“城墙”题。这个标签最大的好处是复习优先级一目了然每个月翻到 T3/T4 列表就知道自己的短板在哪。难点在于这个标签要敢于动态调整。一道题刚做的时候觉得难标成 T3一个月后变体顺手做出来了就把它降成 T2。反过来之前标 T2 的题过段时间卡住了也要果断升回 T3。标签的意义是反映真实水平不是给自己贴面子。4. 题目讲解怎么写才不是抄题解很多人的“题解笔记”最后都会变成对官方题解的复述。这种笔记不能说没用但用处非常有限。真正的讲解应该回答三个问题为什么想到这个方向、为什么最优解是这样、如果自己从零推导会卡在哪。这三个问题能回答清楚讲解就成功了。4.1 先写暴力做法再写优化动机我写思路讲解的第一步永远是先把最暴力的解法写出来。看起来多此一举但事实是几乎每个优化思路都诞生在“暴力解法浪费了什么”这个点上。比如滑动窗口类题目暴力做法是每个起点都重新扩展一次终点它的浪费在于右边界扩大时很多信息已经被扫过一遍却没能被复用。滑动窗口的本质就是把右边界扫过的信息保留下来让左边界逐步收缩而不是从头再扫。这种“浪费点分析”写清楚之后再看官方题解会顺畅很多因为每个优化步骤都有了依据。否则只看官方那段精简代码第一遍很难体会为什么这样写。我见过很多初学者对着最优解发呆不是智商不够而是缺少从暴力到最优的中间推导。刷题本恰好可以把这段推导补齐。4.2 用“五步法”写动态规划讲解动态规划的题最容易“看得懂答案写不出代码”。我给自己总结了一套 DP 题目的五步讲解法定义状态dp[i] 表示“前 i 个元素中满足条件的最大值”初始化dp[0] 的值和原因状态转移dp[i] 是从 dp[i-1] 还是 dp[i-2] 转移为什么遍历顺序从前往后还是从后往前跟状态依赖方向有关答案位置答案在 dp[n] 还是在 dp 数组中扫一遍最大值这套五步框架写下来基本能把一道 DP 题讲透。我再补充一个平时很多人忽略的点写出状态定义时一定要顺带写一句“为什么这么定义”。比如背包题里 dp[i][j] 为什么表示“前 i 个物品容量 j 时的最大价值”因为这样才能反映第 i 个物品取不取的两种分支。这个定义一旦想明白状态转移方程基本就水到渠成了。4.3 讲解是写给自己看的不需要官方腔调还有一个心态问题刷题本里的讲解不需要文采不需要像教科书那样端庄甚至可以很口语化只要自己能看懂。我经常在讲解里写“这里卡了很久发现错在数组越界”这类句子虽然不正式但复习时读起来很真实能瞬间唤醒当时的思考状态。如果记录写得太干净太官方反而像查资料没有代入感。记录讲解的时候我还会刻意用“我”做主语的句子“我所以为的坑在哪里”“我当时没有注意到什么”。这种第一人称记录方式比客观陈述更容易让未来的自己产生共鸣复习时大脑的活跃度明显更高。5. 代码实现部分的规范与模板沉淀代码实现是整个刷题本里最容易被敷衍的模块。很多人直接把题解网站的提交代码存进来里面一堆读入输出逻辑回看时完全看不出核心思路。我后来把自己的规范固定成几条原则虽然多花几分钟收益却非常大。5.1 只保留核心逻辑的独立函数刷题时平台会给你完整的输入输出结构比如 C 的读入、输出Python 的 input()。但整理进刷题本时我会把代码改成“输入参数直接传进来函数内部解决返回结果”的独立函数形式。这样有几个好处复现测试时不用处理控制台核心思路不会被子函数淹没代码更容易拆成模板复用。# 示例两数之和的核心函数 def two_sum(nums, target): seen {} for i, v in enumerate(nums): if target - v in seen: return [seen[target - v], i] seen[v] i return []这种“内存参数直接进结果直接出”的函数在任何环境里都能跑复习时也一眼能看到逻辑全貌。5.2 按范式沉淀模板而不是每题复制粘贴我整理刷题本时会单独维护一个“模板区”存放常见的算法骨架。下面列几个最实用的模板。树遍历的递归模板def dfs(node): if not node: return # 前序先处理当前节点再递归左右子树 dfs(node.left) # 中序先左再当前再右 dfs(node.right) # 后序先左右再当前二分查找模板左闭右开区间int lo 0, hi n; while (lo hi) { int mid lo (hi - lo) / 2; if (check(mid)) hi mid; else lo mid 1; }并查集模板带路径压缩vectorint parent; int find(int x) { if (parent[x] ! x) parent[x] find(parent[x]); return parent[x]; } void unite(int x, int y) { parent[find(x)] find(y); }这些模板不是靠背而是靠新题反复“验证”才能内化。每当你遇到一道新题用到模板就把它拿来改成符合该题目的形态然后对比模板和实际代码的差异。这个过程会逐渐加深对模板适用边界的理解——比如二分模板里什么时候返回 lo什么时候返回 lo - 1光看是看不出来的必须亲手改过几道题。使用模板还有一个隐藏好处因为代码套路固定了新题的思考空间就被腾出来只剩下“这题跟模板的差异在哪”这一个问题解题速度会明显提升。这也是刷题本里单独设“模板区”的核心原因。5.3 复杂度分析要写“为什么是这个量级”我在每次记录里都会写复杂度但不是一个空洞的 O(n)而是备注原因。比如哈希表解法写“时间 O(n)因为只遍历一次数组每次查询 O(1)空间 O(n)因为哈希表在最坏情形下存了 n 个键值对”。这种“结论 理由”的写法比单纯写一个公式更有复习价值也更能帮你形成量级直觉。量级直觉有多重要面试里几乎必问“能再优化吗”如果你能脱口说明这份解法的复杂度瓶颈在哪面试官至少知道你是理解这道题的。而这种能力恰恰是在刷题本里一次次写下“为什么是这个量级”的过程中积累出来的。6. 刷题本闭环复习节奏和入库流程很多刷题本最后变成“写了不看”的仓库根因是缺一套运行机制。刷题本的意义依托于长期使用而存在不是写完就结束。我运行下来最有效的闭环由三部分组成间隔复习、每日入库、状态标记。6.1 间隔复习不是死记硬背而是主动回忆间隔复习的时间节奏因人而异这里给出一套我亲测有效的五轮方案第 1 轮当日整理完睡前口述一遍思路第 2 轮第 2 天只看题目重述尝试复现核心代码第 3 轮第 7 天不看讲解放下重做做不出来再看讲解再补一遍第 4 轮第 15 天做一道同类新题对比旧题的解法第 5 轮第 30 天只重看易错点部分并把讲解压缩到三句话这套节奏不需要死记只要在每条记录的状态字段里留下“下次复习日期”到点统一处理就行。重点在于主动回忆不是重新阅读。哪怕回忆失败也比直接重读印象更深。因为失败本身就是在告诉大脑“这个信息即将被遗忘请重新编码”。6.2 每日入库要克制不能陷入数量膨胀新题入库流程我有明确顺序先卡 30 分钟独立思考想不出来再看题解看完题解必须关掉题解自己重新写一遍写完后立刻整理六模块最后打标签归入章节。这样一条题目入库的时间大约在 40-50 分钟所以每天能消化 2-3 道已经很不错。刚开始总想贪多一天塞五道后来发现五天之后再回看每道题的记忆都薄得像一层纸。量要控制存量更要控制。我给自己定了一条规矩每天入库新题数量不超过当天复习量的两倍。也就是说如果当天有六道题等着复习那最多只入库三道新题。这个配比能保证刷题本不会“只进不出”也避免复习队列越积越厚。6.3 状态标记未消化、可复述、已吸收我在每条记录的状态栏里填三种状态未消化、可复述、已吸收。未消化意味着目前还不能独立写出来可复述意味着能不看讲解讲出思路已吸收意味着能顺手写出核心代码并且能在同类新题中迁移。状态会随复习轮次变化也会随能力波动往回掉——比如某个我以为“已吸收”的题再过两个月做变体时又卡住我会果断把它降回“可复述”。这种动态回溯很重要它会让复习列表永远贴着真实水平。很多人看复习记录越看越心虚就是因为列表里全是“已完成”但真实掌握度早就掉队了。状态字段可以随时修改我每隔两周会统一处理一次状态变化把那些高估或低估的标记修正过来。7. 踩过几次坑之后的改进一些亲测有效的规则做算法刷题本这件事我断断续续坚持了不短的时间中间推翻过好几版方案。有几个坑值得提前说出来能帮后来者少走很多弯路。7.1 工具选择越简单越好我见过有人把大量时间花在搭建笔记模板、配颜色、调插件上说实话这些对算法能力的提升几乎没有帮助。合适的工具只需要满足三个条件能写代码、能全文搜索、能加标签。功能越少越好功能越多越容易在研究工具时过度消耗。我实验过纯文本文件加目录索引也用过带代码块的笔记软件最后稳定下来的是最朴素的一套方案。真正有用的不是工具的高大上而是每天往里面填内容的习惯。工具再怎么简陋只要天天更新半年后就是一笔巨大的财富工具再怎么华丽一个月不打开也只是个漂亮的空壳。7.2 不要变成“题解搬运工”最容易放弃的点是对着一道难题看懂题解然后想把它抄进笔记。此时我会克制住自己合上题解先用自己的话复述一遍思路再写代码。如果复述不出来说明还没看懂需要再看一遍题解或换个角度。经过这个“复述”关卡整理出来的讲解回顾率会高很多。这个动作背后的逻辑很简单看懂别人的讲解是一种被动理解用自己的话重新组织是一种主动建构。前者产生“我会了”的错觉后者才是真学到了。刷题本里如果全是搬运来的内容和自己的思考毫无关系那它对学习的帮助就很有限。7.3 给刷题本预留“半年后回看”的余地最后一个小建议定期回读很久以前整理的老记录。我每隔一段时间都会翻到一个多月前的笔记看着当时的别扭措辞和冗余代码既觉得好笑也能明显感受到进步。这让我更确信刷题本真正的价值是给自己留下一条看得见的学习曲线。它不是给别人看的作品而是未来复习时的一个伙伴。所以每次整理时我都会问自己一个问题半年后的我看到这个条目能不能一眼看懂如果答案是否定的就说明这条记录还没整理到位需要继续补。这个判断标准虽然主观但非常好用。如果能让半年后的自己一眼看透现在的思路这套整理方法就已经及格了。