3月16号这天,我给自己定的刷题安排很明确:二刷基础区107、108、109三道题,再啃下进阶区第51题。看起来只是日常任务清单上的四行字,但真正执行完再回头复盘,我发现这个组合本身就藏着一套挺值得说的逻辑——为什么是二刷而不是新刷,为什么三题基础配一题进阶,以及那天踩过的坑和绕开的路。这篇文章就把这次“3.16刷题日”完整拆开,从题目分类、二刷方法、进阶题拆解到实际操作流程和常见问题,整理成一份可以直接抄作业的刷题复盘。
先说结论:那天我原计划是早上40分钟搞定三道基础题,下午再集中处理进阶题,结果基础题在第三道卡了将近一小时,反而是进阶题一次通过。这种“倒挂现象”其实很典型,也很能说明问题——基础题的“基础”不代表简单,它考验的是对核心思路的稳定掌握,而进阶题的“进阶”也不代表难到够不着,关键是能不能把基础模块灵活组合起来。
1. 刷题方案为什么会这样安排
1.1 二刷到底在解决什么
很多人刷题有个错觉:做过一遍就算会了。但做过和会写之间隔着一条鸿沟。我第一次刷基础107、108、109的时候,三道题都理解了题解,甚至跟着敲了一遍代码,当时觉得“稳了”。结果隔了两周再回头看,第一反应是“这题我做过吗”,第二反应是“题解当时是怎么写的来着”。这不是记性差,而是学习方式的问题。
人的记忆有一个特点:被动复习和主动回忆的效果差别非常大。看着题解点头,属于被动复习,大脑几乎不需要费力就能跟上思路,但这种“流畅感”恰恰是假象——它让你误以为自己掌握了,实际上一合上题解,独立提取解题路径的能力依然很弱。二刷的核心动作不是“再读一遍”,而是“先合上答案自己写”。写不出来再回头看,这时候看题解的效率会高很多,因为你已经知道自己的盲区在哪里。
所以我对“二刷”的定义很简单:它不是重复劳动,而是把“认得的题”变成“能写的题”的必经过程。一刷解决的是“这道题考什么”的问题,二刷才解决“我在没有提示的情况下能不能独立写出来”的问题。这也是为什么我在题单上标了“二刷”状态的题,都会强制要求自己在不看题解的情况下先尝试10到15分钟。
1.2 为什么基础题和进阶题安排在同一天
这个安排不是随手的,它背后有一层刻意设计:进阶题通常不是凭空冒出来的新题型,而是若干个基础知识点组合后的产物。进阶51那道题我在第一次看到时毫无头绪,后来拆解完才发现,它的核心结构其实就是在基础107的链表操作思路上,叠了一层基础108的条件变式处理,再加一点基础109的遍历逻辑。
把基础题和进阶题放在同一天刷,相当于给大脑搭了一座桥:先通过三道基础题把相关模块激活,再立刻面对一道需要组合这些模块的进阶题,整个过程是连续的。相比之下,如果进阶题隔了一周再做,等用到链表反转的时候,那个思路还得花时间重新捡起来,做题效率会差一截。
这有点像体育锻炼里的“专项热身”:直接上大重量容易受伤,先做几组轻量激活,让相关肌群进入状态,再上主项。刷题也是一样,先做三道熟悉的基础题,让“手感”和思维状态热起来,进阶题的成功率会明显提高。
1.3 一天的题量为什么是“3+1”
那天我给自己定的量是三道基础加一道进阶,不是五道,也不是十道。原因很简单:题量从来不是目标,吸收率才是。我见过不少人刷题喜欢一天刷十几道,刷完发个朋友圈打卡,看着很有成就感,但到了周复盘的时候发现什么也没留下。我自己经历过这个阶段,所以后来调整了节奏——宁可一天只吃透四道题,也不要囫囵吞十道。
三道基础题的量,刚好在一次专注力比较充沛的时间窗口里。二刷状态下,每道题从“独立尝试”到“对照总结”大概需要20到30分钟,三道题下来大约一个半小时,不会因为战线拉太长导致后半程敷衍。进阶题单独留出一整块时间,因为这类题往往需要拆解、尝试多种方案、再回头比对基础题里的做法,思考密度和容错空间都需要更高。
另外一个比较现实的原因是:如果某天状态不好,四道题的清单是可以在“完成三道基础、进阶题只做到一半”的情况下收尾的。计划可以留有余地,执行起来才不容易崩。
2. 核心细节:基础107、108、109的题目类型与二刷要点
2.1 先说我用的这套题库的编号逻辑
为了避免读完一头雾水,我先说明一下:我用的不是某平台的固定题库,而是自己维护的一套分类刷题清单。基础区从1开始编号,按“数据结构核心操作”和“常见算法范式”两条线排列;进阶区单独编号,从1开始,每道题会标注依赖了哪些基础题的知识点。107、108、109属于基础区最后段的三个相邻编号,它们之间的难度梯度是刻意排列过的:一道原型题、一道变式题、一道综合题。
这样做的用意在于:如果你只刷题不分类,每道题都是孤立的事件,那么刷100道和刷1000道的区别只是“见过的题更多了”,但遇到没见过的组合方式时依然会卡壳。而当你把题目按“原型—变式—综合”的梯度排列,你会发现所谓的新题,大多都是旧知识点的重新排列组合。二刷的时候,这种分类关系的价值更明显——你会看到三道相邻编号的题之间,原来有一条完整的思维递进线。
2.2 基础107:原型题,练的是“肌肉记忆”
基础107定位为链表操作的原型题。这类题的要求很纯粹:给定一个单链表,按指定规则逆序或者重排。它的核心价值不在于题目本身有多难,而在于它是很多后续题目的“最小功能单元”。就像学写字先练基本笔画一样,笔画不熟,后面所有复杂的字都会写歪。
二刷这道题的时候,我做的事情比较特殊:迭代写法和递归写法都独立写了一遍。很多人在二刷时只写自己最顺手的解法,这其实浪费了复习的机会。迭代解法练的是指针操作的严谨性——什么时候需要保存下一个节点的指针,什么时候切断链接,循环终止条件是prev指向什么,每一步都直接决定程序会不会出现环或者丢节点;递归解法练的是对“递推关系”的理解——你不需要手动追踪每一步指针变化,只需要相信“子问题已经搞定”,然后只关心当前层怎么把结果接起来。
我自己在二刷时迭代写了一遍,花了不到10分钟搞定;然后强制自己不看题解写递归版本,结果卡了两分钟,原因很典型:递归的终止条件写成了循环解法的边界条件,漏了“当前节点为空”的情况。这个细节恰好是二刷的意义所在——一刷时我复制粘贴过递归解法,以为自己会了,其实根本没建立起递归的终止条件直觉。这种错误在面试或实际写代码时很容易被忽略,但它会在某些特殊输入下直接抛空指针。
2.3 基础108:变式题,考的是条件变化
基础108是同一知识领域的变式题。原型题处理的是“整体逆序”,而变式题往往把条件改成“从第m个节点到第n个节点之间逆序,其余保持原样”。表面上只多了一个区间条件,实际上增加的思考量是很大的:你需要先定位区间的起点和终点,需要保存区间前的节点引用,也需要处理区间右边界之后的拼接,而且还要额外考虑两个边界情况——m等于1的时候,区间前面没有节点,这时头指针要改变;m等于n的时候,区间只有一个节点,根本不需要反转。
这题一刷的时候我犯过一个特别经典的错误:只考虑了一般情况,没考虑m=1。结果代码逻辑跑普通用例全对,一提交就报错,因为预判里“保存区间前节点”的那一步,在m=1时直接解引用了空指针。二刷的时候我特意在写代码之前先把三个边界条件列出来,然后才动手。这个习惯是后来慢慢养成的:刷题时先花30秒把空输入、单节点、边界索引这三类极端情况过一遍,再开始写代码,看起来多花了时间,实际上能省下后面调试的一大段时间。
2.4 基础109:综合题,把前面思路合并应用
基础109出现在这一组编号的第三位,是因为它不再是单一知识点的考察,而是把前面的基础能力放在一个更复杂的背景下组合使用。这类题不一定是“难题”,但它的题面往往更长,步骤更多,更重要的是,它要求你对前面几道题涉及的思路都保持在手感之内。
我做基础109时最大的感受是:如果先做了107、108再看它,整个人的思路是连贯的——“原来只是反转整条链,后来是反转区间,现在还要再把排序或者去重逻辑叠上去”。这就好像拼图的时候,你手里已经有两三块拼好了的小块,看到剩下那块怎么嵌入大概就有谱了。相反,如果直接上来做109,你可能会觉得它琐碎、步骤多、不知道从哪下手,因为大脑一次要处理的信息量太大了。
二刷109的时候,我给自己定了一个要求:不要急着敲代码,先在纸上把步骤拆成三步以上。第一步处理什么,第二步处理什么,第三步处理什么,每一步的输入输出分别是什么。写完之后再对照代码,发现我之前一刷时写得很乱的代码其实就是因为没有拆分步骤,把所有逻辑混在一个循环里,搞得自己都看不懂。拆分之后,代码结构清晰多了,而且一旦某一步逻辑出错,定位起来也快得多。
2.5 三道题放在一起二刷,才发现的事
刷完这三道题之后,我把它们码在同一个笔记页里看了一遍,发现了一些单刷时看不出来的规律:107是“整体逆序”,108是“区间逆序”,109是“逆序之后还要继续做别的操作”。三题共享同一个核心操作,但每向后一题,组合的信息量就多一层。这给我的启发是,以后复习任何一组题,都应该主动去找它们之间的“递进关系”,而不是把它们当作三道孤立的题目。
这个发现也直接影响了当天下午做进阶51的状态。因为上午一直在同一知识链条上热身,下午看到进阶51的题面时,第一反应不再是慌,而是迅速映射到“我上午做过的哪几个模块跟这道题有关”。所以如果你现在刷题还是一件一件孤立地刷,我建议你花一点时间把已经做过的题按“知识点-难度-涉及的前置题号”重新整理一遍,效果会非常明显。
3. 进阶51的实现过程与实操复盘
3.1 拿到进阶题,先拆解再动手
进阶51那天我大概花了10分钟看题,没有直接写代码,而是先在草稿纸上做了一件事:拆题。我把题面里所有的操作要求一条条列出来,然后对应到已知的知识模块。它涉及链表操作、条件判断、还要在特定情况下做重排。仔细一比,核心链路就是基础107的反转思路加上基础108的边界处理,最后再叠加一层109里的遍历策略。
拆分完我基本就确定了:这题不是“新题”,而是一道组合题。进阶题和基础题最大的差别不是“知识点更多”,而是“需要组合的知识点更多”。如果你在进阶题上卡住,八成不是因为缺少一个新知识,而是因为某个基础模块的熟练度不够,导致组合的时候频频失速。
3.2 关键步骤与代码实现要点
按照拆解后的顺序,很多题最终都会落到快速排序或归并排序中的一种。这里我先说一个思路层面的共识:时间复杂度要求 O(nlogn) 是面试场景里的一道分水岭。看到这个限制,如果你第一反应还是“插入排序”,那就要警惕了——不是不能写,而是大概率挂掉。进阶51在题干中严格控制了时间复杂度,所以我当时的思考路径非常直接:先排除烧时间的平方级别方案,再在“快速排序”和“归并排序”之间做选择。
链表和数组不一样。数组里可以随意访问中间节点,快排的随机访问优势很大;但链表本身是递归结构,它的天然访问方式就是顺序遍历。归并排序不需要像快排那样反复“分区后向两边跳转”,它只需要稳定地从中间切分、递归处理,再合并两条链。用数据结构的直觉去思考,链表更适合归并排序,因为拆分和合并都是标准的指针操作。
确定了方向之后,接下来就是动手实现归并排序的几个核心环节:找中点、拆链、递归排序、合并两个有序链表。这里有一个细节,大多数人第一次写链表的归并排序都会卡住:快慢指针找中点,但递归的终止条件是“只有一个节点就返回”,因为空链和非空链的边界处理逻辑不同,拆到单节点才意味着子问题已经小到可以返回。快慢指针在找中点时,慢指针从当前头节点出发,快指针每走两步,慢指针走一步,等快指针到达末尾,慢指针位置就是中点。
拆链的时候还有个常见陷阱:只找到了中点,却没有把链断开。如果不把左右两段链彻底断开,递归处理左边那部分时,快指针可能越过中点一路跑到原来链的末尾,最终导致无限递归或者结果错乱。所以拆分环节一定要有两步——不仅要找到中点的前驱节点,还要把它的 next 指针置为 null,把链从中间“剪断”。
合并两个有序链表看似简单,但链表题最容易在这种地方出边界问题。写合并的时候我用的还是那个经典套路:用一个哑节点(dummy node)作为合并结果的链表头。它的好处很直接:你不必额外判断“合并后的头节点究竟来自左链还是右链”。哑节点把这个问题彻底绕开了——你只管从两个链里不断取较小的节点接到哑节点的后面,最后返回 dummy.next 即可。
这是进阶题里最关键的一步,但很多新手因为不熟悉快慢指针和哑节点这套组合打法,以为这题需要新学一个非常高级的技巧,其实不是。它就是在三个基础知识点之上做了一次串联。
3.3 失败现场与一次通过的转机
这个标题看着有点矛盾,但实际上时间线是这样的:上午我看基础109的题面时,第一反应是“这题我在一刷时好像写的很顺”,结果自己真的上手写,才暴露了一刷时“抄得顺畅、理解得粗糙”的问题。所以整个一天里,我真正被卡住的反而是基础题,进阶51反而因为基础思路已经被反复激活,一次就走通了。
这个“倒挂现象”让我意识到一件事:刷题时别只看完成率,要看“独立写出率”。一刷时我写109可能只需要二十分钟,但那是在题解打开着的情况下;二刷时我靠自己写,卡了半个小时,才把每一步彻底想明白。这两者之间的差距,才是你真正需要补的内容。过了这个坎,下午做进阶题的时候那股手感和思路连贯性是完全不一样的。
3.4 完成之后怎么验证与记录
代码写完第一版,我没有直接提交,而是做了一套自测:先用题目给的示例用例跑一遍,确认基本逻辑对;再自己想三个额外的用例——空链表、只有单个节点、完全逆序的链表。这三类用例覆盖面很广:空链表考验的是代码对“没有节点”的处理,单节点验证的是最小子问题,完全逆序的数据则能测试排序是否真的完成了交换。通过这几组测试之后我才提交,一次通过,运行时间和内存占用都在预期范围内。
通过之后,我顺手在笔记里做了个一行记录:题号、状态(完成)、核心方法(归并排序)、依赖的基础题号(107、108、109)、用时的体感、以及一个“一句话思路”。这一行字在今后的复习里价值非常大,它让我的进阶题索引不再是一堆“做过但忘了”的标题,而是一张可以按图索骥的分类表。
4. 完整实操流程:一次可复制的刷题日
4.1 刷题前的准备工作
很多人刷题效率低,不是因为题目难,而是因为准备工作没做到位。我这天的准备工作分三步,耗时不到15分钟。
第一步是“列清单”:前一天晚上,我会把第二天的刷题计划写在笔记本上,格式很简单——日期、题号、刷题类型(新刷/二刷)、预期目标。当晚我写的是:“3.16 二刷107/108/109,完成进阶51。”第二步是“归拢资料”:把我自己做过的笔记、代码模板、以及相关题解链接整理到一个文件夹里,省得第二天到处翻。第三步是“定环境”:提前确认编辑器、编译环境、测试用例都正常,特别是准备了常用的测试函数模板,这样拿到题就能直接跑数据,不用现场写一遍 IO 逻辑。
这一步看似琐碎,但它真的能帮你节省大约20分钟,而这20分钟恰恰经常是你专注力的黄金时间。别把它花在找文件上。
4.2 当日时间线与执行细节
我把我当天的实际时间线整理成了表格,方便你直观看到四道题是如何被拆进一天里的:
| 时间段 | 任务 | 实际耗时 | 执行状态 |
|---|---|---|---|
| 09:00 - 09:30 | 基础107 二刷:迭代+递归独立编写 | 约25分钟 | 顺利,递归边界小卡后修正 |
| 09:35 - 10:05 | 基础108 二刷:边界条件分析与编码 | 约30分钟 | 顺利,提前排查了m=1的情况 |
| 10:10 - 10:50 | 基础109 二刷:分步拆解+编码 | 约40分钟 | 卡在独立归纳步骤,最终攻克 |
| 14:00 - 15:00 | 进阶51:拆题+归并排序实现 | 约60分钟 | 思路连贯,一次通过 |
上午的节奏是每做完一题休息5分钟,喝水、看一眼窗外,让大脑从高强度的指针操作里抽离出来。下午进阶题单独空出一整块时间,因为这种题需要连续思考,中途被打断的代价是很大的。
这个时间表看起来很简单,但它给了我一个很有效的经验:强迫自己在两题之间切个场景。哪怕是起身去倒杯水,都比连续坐在屏幕前刷完四道题要更有利于保持专注。因为刷题的核心不是“坐得久”,而是“始终保持清晰的思路”,适当离开屏幕能快速重置注意力资源。
4.3 刷题后的复盘模板
每天刷完题之后,我都会花10到15分钟做一件事:填写复盘表。这张表是刷题计划里最容易被省略、但最值得保留的一步。它的核心内容有三块:第一块是“错因分析”,如果某道题写错了或者写不出来,必须归类——是边界没考虑到、思路没建立、还是某个基础操作不熟练;第二块是“思路一句话”,用一句话概括这道题的最优解法,比如“用快慢指针找中点,归并合并”,这一句话将来复习时直接可以当作回忆锚点;第三块是“关联题目”,记录这道题和之前做过的哪些题有知识点连接。
这份复盘表不需要长篇大论,关键是要坚持做。我见过很多人刷题勤奋得吓人,但从不复盘,导致同样的错误在几十道题里反复出现。一张复盘表里如果同一类错因出现了三次以上,你就该去定向加练那块短板了。
5. 常见问题与排查技巧实录
5.1 二刷时发现“全忘了”怎么办
这是我的真实经历,也是二刷最常见的挫败瞬间:打开一个月前做过的题,发现自己连题解的思路都回忆不起来,第一反应就是“白刷了”。但我要告诉你的是,遗忘本身就是二刷计划的一部分,它不代表之前的努力无效,恰恰说明你需要把这个知识从被动记忆升级成主动记忆。第一遍学过的东西在长期记忆里本身就不牢固,忘了再捡起来,这个过程正是巩固的关键。
我的处理方法是“找回三道坎”:第一道坎,先自己看题,尝试回忆会用哪种方法,哪怕回忆出来的方法不是最优的,也先记下来;第二道坎,实在想不起来再翻自己的笔记,看当时记的“一句话思路”;第三道坎,如果笔记也看不懂,才去看题解,但看完必须把题解思路用自己的话重新写到纸上,不能直接复制。按这三道坎走下来,哪怕题没做出来,你对这道题的理解深度也比一刷时高一个层级。
5.2 看题解能懂,自己写却卡住的原因与对策
这是刷题圈里最普遍的困扰,经常表现为:题解一行一行看,每一步都“合理”,但合上题解那张纸,脑子里就只剩一堆零散的指针操作,完全无法组合成可运行的代码。这个情况的本质是大脑把“理解别人的代码”误当成了“掌握知识”,它消化的是别人整理好的逻辑,而不是自己组装的路径。
我的对策非常固定:看完题解之后,强制自己用不同的数据例子演示一遍这个思路。把题解里的流程套到一个更小、更极端的输入上,一步一步在草稿纸上模拟,直到自己能解释“为什么这一步要这样处理边界”。能用自己的话讲清楚,才算真正过了“看懂”和“会写”之间的那道关。这个方法适用面很广,从链表题到动态规划题,核心原则都是“把别人的逻辑翻译成自己的语言”。
5.3 做得很慢,一天刷不了几道题怎么办
刷得慢是很多人焦虑的源头,但这里要分清楚情况。如果你每道题都是新题,且完全不会做,看一题卡一题,那是知识地基的问题,应该回到基础区把前置知识点补上。但如果你的问题是每道题都要磨很久才能写对,那反而可能是状态问题或方法问题,而不是能力问题。
我自己排查“慢”的原因时,会先问三个问题:是不是前置准备工作没做,导致做题时还要现场查API和工具函数的签名?是不是做题顺序不合理,比如先从最难的进阶题入手,结果消耗掉了全部精力?是不是没有使用分类刷题清单,让每天的题目在知识点上毫无连贯性?排除掉这三个干扰项之后,如果速度还是上不来,那就再降低题量,比如从“3+1”降到“2+1”,把吸收率作为优先级。慢不可怕,可怕的是用战术上的勤奋掩盖战略上的低效。
5.4 基础题做明白了,进阶题依然无从下手
这个问题也经常被小伙伴问到。明明基础107、108、109都能写顺,进阶51却还是不知从哪下笔。我的经验是:你缺的不是基础,是“组合前的拆解训练”。进阶题里通常糅合了多个知识点,它们不会按顺序排队出现,而是打乱交织在一起。如果你没有主动锻炼过“从题面中提取可识别模块”的能力,那面对组合题时就会像看一堆乱麻,理不出头绪。
我训练这个能力的方法是:每刷完一组基础题,强制自己写三道“依赖这些基础题的进阶模拟题”的拆解步骤,不用完整写代码,只写思路。比如写清楚“第一步先用某个基础题的思路定位核心结构,第二步把另一道基础题的处理逻辑嵌入进去”。这个练习做完之后,再看到进阶题时,你会发现自己能更快地在题面里认出那些熟悉的“零件”,组合起来就不再像天书了。
5.5 刷题记录管理的几个实用小技巧
最后聊一下刷题记录这件事,因为它直接决定你的二刷、三刷效率。我的管理方案很简单:用在线表格维护一个题单,每一行一道题,列包括日期、题号、状态(新刷/二刷/三刷)、难度、知识标签、一句话思路、错因归类。并用文本索引做补充:每条记录的最后附上题号、核心方法、依赖知识点,每周抽一天随机回看这个索引。
这套体系帮我解决了一个很实际的问题:刷过的题不再是无序堆积,而是变成了真正可以反复利用的知识资产。标准化一行记录只需要30秒,但它能帮你在几周之后节省好几个小时的复习时间。
我个人在这套刷题流程里最深的体会是:刷题不是比谁“刷过的题多”,而是比谁“沉淀下来的知识多”。基础题二刷的意义,是把那些看似简单但极易生疏的核心操作内化成一种本能反应;进阶题的训练意义,则是学会把多个基础模块组合成一个完整的解决方案。3月16号这天,我用四道题验证了这个循环的有效性,也希望这篇复盘能给你接下来的刷题计划提供一点参考。最后再分享一个小技巧:如果你打算安排类似“3+1”的刷题日,一定要在当天结束前写下那一行一句话思路,因为趁记忆还热乎时记录的效率,是第二天再补记录时完全比不了的。