第41天,说实话有点难熬。不是题目变难了,而是那种“今天要不就算了”的念头又冒出来了。前四十天积累的惯性还在,但新鲜感早就耗光了,剩下的全靠纪律硬撑。我清楚记得今天早上打开练习页面之前,先给自己做了三分钟心理建设。但等真正坐下来,把一套题跑完,那种“今天的代码又跑通了”的踏实感,还是像前四十天一样准时出现。我想把这段经历拆开讲讲,尤其是第41天这个节点上,我复盘出来的那些练习方法、踩过的坑、以及支撑我继续坚持下去的东西。如果你也在用类似的方式做编程练习或技能训练,这篇文章应该能帮你少走一点弯路。
1. 这41天我到底练了什么:一套练习系统的搭建过程
1.1 最初两周的混乱期:为什么不能只靠感觉练习
很多人以为坚持练习就是每天打开题库、随机挑两道题、写完提交就算完事。我前两周基本就是这种状态,结果很糟糕:每次练完感觉都懂,但过两天看到同类题还是觉得陌生。
后来我才意识到问题出在两个方面。
第一是选题没有梯度。随机选题会陷入一个极端:有时连续碰简单题,练得毫无压力,形成虚假的完成感;有时又连续碰困难的题,一道题卡三个小时,直接消磨掉第二天的动力。这种节奏既没有巩固基础,也没有有效提升,纯粹是在用“量”安慰自己。
第二是缺少知识体系。我练习的内容是编程算法与数据结构,这类知识是网状结构,数组、链表、栈、队列、哈希表、树、图、贪心、动态规划之间都有前后依赖关系。今天做一道图论的题,明天跳去练动态规划,知识点在脑子里串不起来,自然就“练了又好像没练”。
调整之后,我给自己定了一个简单的规则:按专题过,一个专题至少练一周。比如第一周只练数组与双指针,第二周只练哈希表与字符串,第三周进入链表和递归。每个专题内再从基础题到进阶题逐级加码。这个调整看起来朴素,但效果立竿见影,至少我在遇到一道新题时,能第一时间判断出它属于哪个知识板块,随之想起对应的常用解法。
1.2 从刷题到刻意练习:练习节奏的重新设计
确定按专题练习之后,第二个问题浮出水面:每天练多少才算合适?
我试过一天五道题,连续三天之后人就开始飘,感觉什么题都会,但一到第四天碰到稍微变形一点的题就露馅。我也试过一周只练三道题,结果因为间隔太长,每次重新进入状态都要花大量时间热手。后来我把节奏稳定在了一天两到三道,这个量能保证持续的热度,又不至于让大脑过载。
更重要的是,我把“练习”本身拆成了三个环节:限时思考、独立编码、深度复盘。限时思考就是拿到题先自己设计思路,不急着看题解。这个环节我给自己卡20分钟,如果20分钟没思路,直接看题解,然后合上题解,凭理解自己重新写一遍。独立编码就是从读题到通过测试用例,全程不看参考代码。深度复盘是把每道题的思路、坑点、复杂度分析、题目变形方向都记在一个文档里。
这三个环节缺一不可。我以前总觉得“看懂题解=会了”,但事实上看懂和独立写出之间隔着一条巨大的鸿沟。只有到了“闭上眼睛能把这个思路讲出来”的程度,这道题才真正算是你的。第41天这两个多小时里,我完整走完了一套这样的流程,下面详细拆一拆。
2. 第41天的实战复盘:一次完整的上机练习怎么走完
2.1 动手前的三分钟:读题、拆条件、定复杂度
今天的练习题目是一道典型的树上路径统计问题。抛开具体细节,这类题的核心套路其实很固定:确定遍历方式、维护路径上的状态、思考合并方式。
我拿到题之后没有立刻敲代码,而是先做了三件事。
第一,把题目要求的输出类型圈出来。是求数量、最大值还是方案数?这决定了算法的基本方向。今天这题是求满足条件的路径数量,我马上联想到前缀和加哈希表的经典组合。
第二,把数据范围标出来。节点数量和其他数值的规模直接决定算法的复杂度上限。今天的范围是节点数小于等于10的五次方,这意味着O(n方)的暴力做法必然超时,需要用O(n)或者O(nlogn)的解法。
第三,在纸上画一棵小树,手动模拟一遍示例。别小看这一步,它能在5分钟内帮我验证思路是否偏离题意,省掉后面调试半小时的麻烦。
这个过程大概花费三分钟,但它几乎决定了这道题能不能在有限时间内写对。我之前有一段时间比较迷信“看到题马上敲代码”,总觉得考虑越想越焦虑,不如直接动手试错。结果经常是写完半行忽然发现思路漏了条件,只能推翻重来,反而更慢。现在我的习惯是,三分钟读题拆条件,宁可先把边界情况写在草稿纸上,也不要让脑子带着一团模糊的念头直接进入编辑器。
2.2 编码过程中的关键习惯:先跑通再优化
很多人写代码的默认习惯是一边写一边“优化”,写着写着觉得某个地方能加个缓存、某个循环能少算一次,于是中途停下来改结构。我吃过这个亏太多次了:思路本来就还没完全成型,中途优化会导致上下文丢失,最容易引入莫名其妙的状态错误。
今天这道题我给自己定的策略是:第一版代码怎么直观怎么来,先保证逻辑清晰、能跑通示例。用一个字典存前缀和的累计次数,通过递归遍历节点,在进入子节点时更新状态,离开时回退状态。整个结构就是标准的自顶向下DFS加回溯,代码写出来大概四十行,谈不上优雅,但每一步都对应着刚才画的那棵小树上的动作,心里非常踏实。
这里有一个很容易被新手忽略的细节:避免隐式状态污染。因为我的前缀和计数是一个全局字典,树的左右分支会共享同一个字典。如果不做“进入时加一,回溯时减一”的对称操作,那么一个分支里记录的结果就会串到另一个分支里,导致最终答案错得莫名其妙。这种“回溯撤销”的思想在很多题目里都会出现,比如全排列、子集枚举、记忆化搜索,理解一次就能用很久。
第一版代码跑通示例之后,我才开始进入优化阶段。优化的重点不是让代码炫技,而是把复杂度降到满足数据范围的要求。今天的优化思路也很清晰:把每条路径上节点值求和的表达式变形为前缀和之差,然后用哈希表抵消掉内层循环。这种先求正确、再求高效的顺序,让我在设计优化方案时心里始终有底,不至于为了优化而优化,最后连正确性都无法保证。
2.3 提交之后的复盘流程:错误记录与知识点整理
代码通过测试的那一刻确实爽,但我现在更看重提交之后的那十分钟。今天写得比较顺,第一次就AC了,但这并不意味着复盘可以跳过,反而是最重要的环节。
我会做三件事。
第一,看看别人有没有更简洁的解法。同一道题,有人用两次遍历,有人用一次遍历维护状态,有人用迭代栈代替递归。每种解法的切入角度不同,背后体现的思维模型也不同。哪怕我今天的代码AC了,我也会把讨论区的高票答案扫一遍,一旦看到不同的切入点,就用自己的话在笔记里复述一遍。
第二,翻错题本。我建了一个专门的文档,记录过去四十天里每一道让我卡住或者提交失败的题目,每条记录包含:当时的错误代码、错误原因分类、正确思路、同类型题目的关键词。今天这道题虽然没报错,但它的思路和上周一道用前缀和解法的题高度相似,我直接在笔记里建立了一个关联索引,让新旧知识点互相打通。
第三,估算“如果再写一遍,能不能一次写对”。这是我给自己定的主观指标,如果答案是犹豫的,我就会再趁热独立写一遍。今天的题属于这类“该趁热再写一次”的情况,因为递归回溯和前缀和组合的细节多,多写一遍能显著提升肌肉记忆。
复盘这件事,真正坚持下来的人并不多,但它恰恰是练习中回报率最高的一环。不夸张地说,前四十天里我至少有一半的进步来自复盘,而不是来自写代码本身。
3. 四十天踩坑实录:这些效率杀手最容易被忽略
3.1 第一类坑:练习方式低效
先说一个最隐蔽的坑:长时间死磕一道题,美其名曰“独立思考”,其实是在边际效应里空转。前两周我经常跟一道中等题死磕两三个小时,中间反复尝试、反复推翻,大脑已经进入低效循环,只剩焦虑在推动手指。后来我给自己立了一条规则:二十分钟想不出清晰思路,就果断看题解,看懂之后合上题解重新写。这道题带给我的收益,远大于在它上面空耗两小时。
另一个坑是只练新题、不复习旧题。人的记忆曲线上写着呢,一个知识点七天内不碰,留存率会掉得非常快。我以前觉得复习就是浪费时间,毕竟“题都做过了有什么好看的”。后来我把复习设计成另一种练习:不重新查答案,直接在白板上把关键步骤和复杂度分析讲给自己听。这个方法比单纯翻笔记有效得多,因为它强制让脑子从“识别型记忆”切换到“主动型重构”。
还有个非常常见的坑是眼高手低:简单题看不上,总想做有挑战性的题。但整棵知识树的根扎得不深,其实很难撑起高难度的枝条。基础题的价值在于让你快速建立“套路反射”,这种反射在高阶题里会成为默认的底层能力。我有一周回头巩固了数组双指针的基础题,之后再去做滑动窗口的变种,明显感觉思维衔接顺畅很多。
3.2 第二类坑:环境与工具问题
上机练习,说到底就是坐在电脑前写代码,这个“环境”本身也能产生大量时间损耗。
首先说的是网络和平台稳定性。我目前使用的在线评测平台偶尔会有负载高、排队等待的情况,这种事如果发生在晚上八点那种高峰时段,一卡就是十来分钟。我的应对方式是错峰练习,尽量把固定练习时间放在晚饭前,或者中午的碎片时段。如果你也有固定练习计划,务必想清楚什么时段你状态好,同时平台的排队压力最小。
其次是本地环境的一致性。有些人习惯在网页编辑器里直接写题,写完就交,不关心本地环境,结果一旦参加模拟赛或者需要调试复杂样例时就开始手忙脚乱。我的做法是搭好一套本地命令行模板,包括标准输入输出、性能计时和几个常用的测试用例文件。这个模板虽然前期花了点时间搭,但之后每次练习都跑在相同环境里,遇到问题也好排查。
最后是代码补全工具的双刃剑效应。现在的编辑器都有强大的自动补全,我前半个月几乎被它惯坏了,写一道题只要有提示,思路就跟着走,等到面试手写代码或者换到无补全环境时,忽然连常用容器的API都要想一下。后来我有意识地每周抽三天关闭自动补全练习,手写常用方法签名和导入语句。这个动作很笨,但确实是防止能力退化的有效办法。
3.3 第三类坑:心态和身体管理
这类坑我觉得最值得展开说,因为大多数人不是输在天分上,而是输在状态起落里。
第一个心态陷阱就是“连续打卡断掉了就完蛋了”。我第19天的时候因为临时加班错过了练习窗口,当天晚上焦虑到不行,第二天居然产生了“要不这个系列就到此为止”的念头。后来我想通了:坚持的本质不是不断更,而是不断恢复。断一天就用第二天补上,断一周就重新调整节奏,重要的是不要因为一次中断就否定全部积累。
第二个陷阱是拿别人的进度当标尺。我在一些学习群里看到有人晒“第60天攻克线段树”“100天从零到offer”,一旦开始比较,心态很容易失衡。每个人基础不同、可用时间不同、目标不同,比较这件事毫无信息量。我后来的做法是每天只记录自己的进度曲线,比如“今天比昨天少犯一类错”“本周复现同类型题目的速度比上周快”,用纵向对比替代横向对比。
身体这块更要命。第36天到第39天我明显进入疲惫期,连续几天练习效率很差,看题目都慢半拍,好几次编译错误甚至没查出来。后来我意识到是连续四十天没有安排有效休息,大脑的注意力资源耗尽了。于是第40天我强制自己没碰代码,睡了一个饱觉,出去散步一小时。第41天再坐到屏幕前,那种“脑子转得动”的感觉又回来了。身体不是练习的旁观者,它就是练习的一部分。
4. 支撑我坚持41天的底层方法论:反馈、输出与休息
4.1 建立即时反馈:让每次练习都有结果
能坚持四十多天,光靠意志力是不现实的,真正起作用的是有效反馈。
反馈分即时反馈和延迟反馈。即时反馈来自在线评测:代码跑通测试用例,马上显示结果,这种感觉就像游戏里的分数跳动,天然有驱动力。但即时反馈也有副作用,就是让人沉迷于“AC”的快感,忽略了更重要的隐藏反馈:代码的复杂度、边界的完备性、思路的表达清晰度。
所以我在即时反馈之外刻意设计了一个延迟反馈系统。每周末我会把这周做过的题重新扫一遍,重点看两类记录:提交失败次数特别多的题目、当时花了很长时间才想到思路的题目。如果过去一周这类题目数量明显下降,说明这个阶段的方法有效,下周可以保持;如果停滞甚至反弹,我就会调整专题顺序或者降低难度。这种“以周为单位”的反馈回路,比每天盯着AC数更有指导意义。
4.2 用输出倒逼输入:写博客和讲题
第41天复盘的时候我翻到一个很有意思的现象:前二十天练习中,很多我自以为理解得很透的题目,一旦想把它写成一篇文章或者讲给别人听,都会发现自己其实还有解释不清的地方。那些含糊之处,恰恰就是理解的黑洞。
这个发现让我养成了一个习惯:每周挑一道最值得一提的题,写一篇尽量通俗的技术复盘,把思路、复杂度、变形方向都写清楚。写这个过程本身就是在重新构造知识结构,因为键盘敲下去之前,脑子里必须有一条逻辑自洽的推理链。如果有人来问,我还会尽量用白板把题讲一遍,讲的过程中被问住的瞬间,就是我优先要补的知识点。这个方法比再刷十道题更能暴露问题,强烈推荐你也试试。
4.3 接受平台期:休息也是练习的一部分
大概在第29天到第35天之间,我进入了一段非常明显的平台期:每天照常练习,题目也能做出来,但总感觉没有实质提升,知识体系不再增长。那种“原地踏步”的感觉让人很挫败,甚至比做题做错更让人想放弃。
后来我复盘了平台期的成因。一是前期进步太快,基础性的东西迅速被补上,后续进入的是需要长期沉淀的硬骨头;二是练习套路固定化,每天都是做题、复盘、记录,缺少新鲜刺激。想明白之后,我做了两个调整:第一,每周引入一次“探索型练习”,不做标准评测题目,而是自己设计一个小项目或者改造某个已知算法的实现;第二,主动降低一段时间的目标权重,把“进步”重新定义为“保持手感”,而不是“学到新东西”。平台期的意义在于消化和整合,它跟快速增长期同等重要。
所以第41天这个节点,我没有给自己安排新的高难度专题,而是完成了一次完整的基础流程练习,再把过去几天积累的笔记做了系统化的整理。这种“退半步再前进”的节奏,是我目前找到的最可持续的练习方式。
一点个人体会
写到这儿,第41天的练习记录本身已经说完了。回看这四十天,我最想分享的并不是某道题的解法,而是那个关于“如何把练习持续下去”的系统:按专题推进、拆解练习环节、认真复盘记录、及时休息调整、用输出来验证理解。这些方法听起来都不新鲜,但没有哪一条是只靠知道就能生效的,必须一天一天做出来。
第41天的题目不算难,但坐在屏幕前把它独立写完、跑通、复盘的那个过程,仍然让我踏实。明天我会继续第42天,也许又会有新的卡壳、新的报错、新的“早该想到”的懊恼。但这就是练习本来的样子——它不会在某一天突然变得轻松,只是你会发现,那个“想放弃”的念头出现的频率,正在一点一点往下走。