刷题记录这东西,看着不起眼,真要坚持下来却比刷题本身更考验人。我自己的账号hetao1733837上,过去三天(3月10日到12日)正好完成了一轮密集刷题,总共21道,覆盖双指针滑动窗口、二叉树递归和动态规划三条主线。整理完这三天的记录,我最大的感受是:记录的价值不在“今天又AC了几道”,而在它能把零散的思维过程变成以后能复用的经验。这篇不打算贴流水账,而是把这三天的题单设计、记录方法、踩坑过程和一些反复出现的解题套路都拆开讲清楚,让同样在刷题的朋友能直接拿过去参考。
先说这三天适合谁。如果你已经过了“连题目都读不懂”的阶段,开始能写一些简单题的解法,但遇到中等偏上的题目经常卡住、测试用例过不全、看了题解又觉得自己啥也没学到,那这篇应该能给你不少启发。我这次的所有题都不涉及冷门数据结构,不考脑筋急转弯,全部是面试笔试中出现频率很高的经典问题,所以题单本身的含金量还是可以的。
1. 三天刷题计划的整体设计思路
1.1 为什么把周期定成三天
我见过很多人刷题特别喜欢定一个月计划、百日刷题计划,结果往往是前一周热情高涨,后面就直接断档。我自己试过几次“长假特训”,发现到了第五天就开始疲,效率断崖式下跌。后来就改成一个周期只做三天,大概对应一个周末加一个工作日晚上,或者反过来三个工作日的碎片时间,无论多忙都能挤出一点保证余量。
三天的好处在于:它足够完成一个相对完整的小闭环——从选题、做题、整理模板,到复盘、应用、查漏,可以不紧不慢地走完;又不会长到让新鲜感耗尽。这次我严格按三个晚上的节奏来,每晚大概一个半小时到两个小时,白天碎片时间看看题解和别人的讨论,整体没破功。
另外,三天也是形成一个“专项”的比较合适的边界。单一知识点的题目连续做三天,到第三天基本能看出自己是否真正掌握——如果换个包装还是不会,说明还没到火候;如果可以快速识别考点并写出稳定解法,那这个点就算过关了。
1.2 题量目标与难度配比
我这次定的目标是三天21题,平均每天7道。这个数字经过一点计算,不是拍脑袋拍的:每晚有效刷题时间约100分钟,简单题平均8-10分钟,中等题平均15-25分钟,难题可能要35到45分钟甚至更久。如果全做中等题,一晚上7道确实偏紧,所以需要留2到3道简单题用来热身和保持节奏。最终的实际配比是:简单7道、中等10道、困难4道,刚好在“挑战但能坚持”的区间。
这里有个小经验:不要每天都塞困难题。困难题最消耗认知资源,处理起来经常要读题加思考超过30分钟,连续做容易产生强烈的挫败感。我习惯把困难题安排在某些专项的第三天,那时已经在前两天的题里积累了足够的熟练度,再碰难题会更有底气。
1.3 这轮刷题想定向解决什么
跨平台、跨公司的题目千变万化,但不能每次随机乱做。我这次选的方向非常明确:
- 双指针与滑动窗口:处理数组和字符串问题的万能套路,很多暴力超时的题都能用它优化到线性;
- 二叉树递归与DFS/BFS:递归思维是后续动态规划、回溯、图论题目的基础,二叉树是这个思维的绝佳练习场;
- 动态规划入门到进阶:我最大的薄弱点,之前一看状态转移方程就头大,这次想系统地练一串递进式的题目,从记忆化搜索逐步过渡到真正的DP。
三个方向按天数分配:第一天全部给双指针和滑动窗口,第二天集中做二叉树,第三天转为动态规划。看似互相独立,其实暗含连贯性——第一天练的是“通过单调性减少枚举”,第二天练的是“把问题分解成子问题”,这两件事恰恰是第三天DP状态转移的核心思维。等到第三天做DP时,很多题看上去已经不是新题了。
2. 刷题记录模板与复盘方法
2.1 一个真正能坚持的记录格式
很多人记刷题记录就是简单抄一下题号和答案,回头翻的时候完全不知道当时在想什么。我用的格式复杂一点,但字段都经过筛选,长期维护成本低,信息密度高:
| 字段 | 内容示例 | 为什么记录 |
|---|---|---|
| 日期 | 2026-03-10 | 看自己节奏是否稳定 |
| 题目主题 | 无重复字符的最长子串 | 直接对应考点 |
| 类型 | 滑动窗口 | 方便归并同类 |
| 难度 | 中等 | 评估配比是否合理 |
| 耗时 | 18分钟 | 判断是否卡太久 |
| 错误类型 | 边界条件-窗口左移 | 错题分类汇总用 |
| 核心收获 | 右指针固定右移,左指针在条件不满足时收缩 | 提炼成自己的模板 |
| 复习标记 | 下周复习 | 安排遗忘曲线 |
这个表格看起来字段不少,但每题其实只需要两三分钟就能填完。真正关键的是“核心收获”这一列,我会用一句大白话把解题思路写下来,比如“这题就是双指针:右边的先进,左边根据约束条件后退,中间维护一个窗口状态”。这种用自己的话写的解释,比复制题解里的官方描述有用得多,因为大脑对熟悉的语言加工过的信息记得更牢。
2.2 卡住多久应该看题解
这是刷题圈争论最多的问题之一。我的标准是:中等题15分钟没有完整思路,难题20分钟没有从样例反推出可行方法,就立刻转去看题解。很多人觉得看题解就是失败,其实不然,千万别在不该花的时间上硬耗。
看题解也不是直接往下翻答案。我的操作是:先只看思路的第一句话,比如“使用滑动窗口,维护一个哈希表记录字符最后出现的位置”,然后就关掉题解自己重新推。这样既避免了在原地打转,又保留了主导解法的思考过程。如果只到第一句话就能完全写出来,这题在记录里会专门标一个“提示即可AC”的标签,说明这个考点已经接近掌握。
真正的问题不是卡15分钟还是20分钟,而是看了题解之后直接抄,抄完就下一题。那种行为不叫刷题,叫打字练习。看题解后至少要能独立写出代码并让它在评测系统里通过,这步我从不省略。
2.3 错误分类与错题本设计
把做错的原因归成五类,方便后续定向补弱:
- 思路缺失型:完全不知道用什么算法,这种题需要整理算法的适用信号;
- 边界条件型:数组越界、空输入、单元素、负数,这类细节纯靠经验积累;
- 实现转化型:思路清楚但代码写不出来,说明coding基本功还不够;
- 复杂度不达标型:能做出来但超时,需要思考更优解法;
- 理解偏差型:读题读漏条件,或者样例看错,老司机也会翻车。
这次21道题里,我最主要的错误来源是第二类和第四类,尤其是滑动窗口题目经常忘记循环里收缩指针的时机,动态规划则是用二维数组的思路推到了一维怎么办、dp数组初始化的边界到底定义什么语义。错题本按这五类标记之后,哪块弱一目了然——我明显看到“边界条件型”占比接近四成,说明以后刷题前要刻意给自己加一堆边界用例再提交。
3. 三天题单详解与核心题型拆解
3.1 Day1:双指针与滑动窗口专项
第一天一共7道题,简单3道、中等3道、困难1道。整体逻辑是从最基础的相向双指针,逐步过渡到同向双指针(也就是滑动窗口)。
先说相向双指针。典型场景:在一个有序数组里找两个数,使得它们的和等于目标值。最直觉的写法是双层循环,但时间复杂度是O(n²)。改用一左一右两个指针,如果当前和大于目标就把右指针左移,小于目标就把左指针右移,这样最多O(n)就能搞定。这个解法的核心依据是单调性——因为数组有序,指针移动的方向才有确定的意义。我第一晚先做两题找感觉,这类题代码量少,价值在于建立“双指针优于暴力枚举”的敏感度。
然后进入滑动窗口。滑动窗口的套路我总结成四句话:右指针负责扩,左指针负责缩,窗口数据在扩缩时同步更新,答案在每次右指针移动后统计。以“无重复字符的最长子串”为例,它的关键点是左指针跳到重复字符上次出现位置的下一位,而不是一位一位挪。我第一次写就错在这里,只想到while左移,没利用哈希表跳到直接位置,结果最坏情况退化成O(n²),被测试数据直接教做人。后来改成存储字符最近出现的位置后,时间降回O(n),判题一次通过。这也是为什么滑动窗口题特别适合“复杂度不达标型”错误——思路对了但细节不对,性能就露馅。
当晚的困难题是“最小覆盖子串”这类变体,本质仍是滑动窗口,但多了个need变量来统计缺口。这类题的通用写法骨架,我在下方代码里用伪代码的形式展示出来:
初始化窗口数据结构,如哈希表need、窗口计数window、left=0、valid=0 for right in range(len(s)): 把s[right]加入window 如果window中的计数满足need,valid加1 当valid等于需要覆盖的所有字符数时: 尝试更新答案 把s[left]移出window 如果window中的计数不再满足need,valid减1 left加1 返回最小窗口这个骨架几乎能套用于绝大多数“子串/子数组最值”问题。第一天结束,我的记录表里已经能看到:双指针从“知道两个指针”进化到“知道什么时候移动哪个指针、移动后更新什么”的层面。
3.2 Day2:二叉树的递归与DFS/BFS
第二天同样7道题,把重点放在“递归处理树结构”的两种经典写法:自顶向下的前序式递归,以及自底向上的后序式返回合并。这两类写法几乎覆盖了二叉树80%的题目,剩下的则是层序遍历的BFS。
关于递归,最需要扭转的思维是:不要试图在脑子里完整展开每一层调用栈。我第一天做“二叉树的最大深度”,还在一层一层手推函数调用,到“判断平衡二叉树”这种需要子树返回是否平衡时,彻底想晕了。后来改用“假设子树已经处理完,当前层只用它的结果做判断”的思维,代码很快就清晰了。这是一种典型的“信任递归”的心态,刚开始别扭,习惯之后效率大涨。
层序遍历的BFS,模板相当固定,我用一个简单的示意代码帮助记忆:
queue = 初始放入根节点 while queue 不为空: level_size = len(queue) 当前层列表level = [] 循环level_size次: 弹出一个节点,加入level 如果左孩子存在,入队 如果右孩子存在,入队 把level加入结果列表 返回结果这个模板优先用len(queue)固定当前层节点数,而不是直接在while里弹出,这样层级才不会乱。第二天晚上我把层序遍历、右视图、锯齿形遍历放在一起做,其实就是同一个模板改一点点输出策略。练习到这种程度,BFS基本就能形成肌肉记忆。
这里有一个必须提醒的细节:递归深度。在处理较深二叉树时,Python这类语言默认递归深度约1000层,有些构建得很极端的测试树可以直接把程序干到“递归爆栈”。要注意两点:一是能转成迭代就转成迭代,二是递归函数先想清楚终止条件和返回值语义再动手。第二天我就碰到过一次递归深度超限,后来用显式栈模拟递归,问题立刻消失。
3.3 Day3:动态规划基础过渡
第三天7道题,难度拔高:简单1道、中等4道、困难2道。内容从记忆化搜索开始,一步步过度到标准DP写法。这个过渡我认为是学DP唯一顺畅的路径——千万不要一上来就啃状态转移方程。
以“爬楼梯”为例,第一步先用递归解决:比如f(n)=f(n-1)+f(n-2)。但直接递归会重复计算大量子问题,这时引入记忆化数组缓存中间结果,就得到了“记忆化搜索”。第三步才是观察计算顺序,自底向上用数组填表。先递归、再记忆化、最后迭代填表,三个步骤逐层递进,状态转移方程是在这个过程中自然涌现的,而不是从天而降的。
当天最耗时间的困难题是编辑距离系列。它的二维DP数组是经典模型,状态定义为“前i个字符和前j个字符之间的最小操作次数”。转移时,如果两个字符相等,直接继承左上方结果;如果不相等,取增、删、改三个操作的最小值加一。这道题我完整走了“暴力递归—超时—加记忆化—通过—整理迭代DP—再AC”的全流程,整个过程耗时约70分钟,但这个完整链条比刷五道简单题都值钱。
Day3结束后,我把三天的知识线画了一张思维导图:双指针利用单调性降低枚举维度,二叉树递归本质上就是把大问题分解为子树问题,DP则是在子问题有重叠时缓存计算结果。这三者在“分治与优化”这个主题下顺利汇合,对后续学习图论、回溯、贪心都能平滑迁移。
4. 实操过程:从读题到AC的完整链路
4.1 读题与样例分析的要点
很多人做题习惯看完题目立刻写代码,这是最低效的做法。我这次执行了一套固定的破题流程,每一步都有明确产出。
第一步,圈出题目的输入规模。看到“数组长度最大10^5”,那O(n²)基本可以直接枪毙,暴力枚举方案放弃。看到“数据量只有100”,O(n²)甚至O(n³)都可能接受。这个快速复杂度预判,极大地节省了试错时间。
第二步,自己动手跑两遍题目给的样例。这一步很多人会跳过,但它能验证对题意的理解正确。我会把样例输入拆成边界场景,在纸上模拟指针或状态如何变化,顺带检查有没有“题目没明说但样例暗示了”的特殊要求。比如滑动窗口类题,有的要求返回窗口本身,有的只要求返回最大长度,这决定了记录窗口状态时是否要额外保存下标。
第三步,强迫自己用三句话总结题意。例如“在一个只包含小写字母的字符串中,找最长的连续子串,使得其中没有重复字符,返回长度”。能说清楚这句话,说明你已经抓住了题目核心,后续即使写不出代码,查题解时也能精准定位知识点。
4.2 从思路到伪代码的关键一跳
思路正确但代码写不出来,是很多人的常态。我习惯先把伪代码写出来再写真实代码。比如做滑动窗口时,先写“右指针移动;更新窗口;while条件不满足:左指针移动;更新答案”,然后再把每个步骤对应成具体的数据结构操作。
伪代码的作用是隔离“逻辑设计”和“语法实现”两个层面的复杂度。先只关注逻辑,不管语法细节;等逻辑清晰了,再一次性翻译成代码。这样写出来的代码明显比边想边写干净得多,出错率也大幅下降。我统计过这次三天里,凡是先花5分钟写伪代码的题,平均调试时间少了差不多一半。
伪代码里我还会显式标出边界条件的处理位置,比如“注意当left右移时,要同步更新窗口内的字符计数”“dp数组初始化时,第0行第0列的含义要写注释”。这些注释可能看起来不重要,但在你回看记录时会非常有用,能一下子回忆起当时的坑在哪。
4.3 复杂度估算与优化取舍
判题系统里常见的错误除了答案错误,就是超时和内存超限。在提交前做一次复杂度自测是很必要的习惯。比如“两数之和”,用哈希表能把时间从O(n²)降到O(n);但如果数组本身已经有序,双指针在空间上更优,不需要额外哈希表。
我在Day1做一道有序数组双指针题时,先写了哈希表的解法提交通过,但看到提示里要求“尝试不使用额外空间”,才意识到自己的解法不够符合题目倾向。后来换双指针重写,代码长度更短,空间O(1)。这件事给我的教训是:AC不一定是终点,判断一道题是否掌握,还要看是否理解了题目设计者想考察的解法。用最优复杂度思路写答案,面试时才能说出“为什么不用额外空间”。
做题时我还习惯在提交前先心算一次最坏情况:将n=10^5代入我的复杂度公式,看运行时间上界大约是多少毫秒。如果明显在这个量级会跑不动,就回过去重新想,不要抱着侥幸心理提交。
4.4 一个晚上完整刷题的节奏控制
这三天的晚上,我按下面这个节奏执行,稳定而且不容易疲惫:
| 时间段 | 内容 |
|---|---|
| 0-15分钟 | 热身:复习昨天的错题和模板,做一道简单题 |
| 15-70分钟 | 核心期:刷2-3道中等题,每道不超过20分钟 |
| 70-100分钟 | 攻坚期:再刷1道困难题或整理复杂题类型 |
| 最后10分钟 | 填写记录表,归类错误,安排次日计划 |
这个节奏的关键是“热身—核心—攻坚”的递增。一上来直接做难题很容易挫败,先用简单题把状态调动起来,再逐步加强。而且我严格设定核心期每道题的时间上限,超了就标记超过,不恋战。这样即保证了题量,也保证了每天都能留下完整记录。到第三天晚上结束时,我还给自己留了一个小尾巴:把Day2和Day3的所有错题重新扫一遍,确保不是“当晚会做,第二天全忘”。
5. 每天刷题后的总结与知识点串联
5.1 用知识图谱代替零散记忆
曾经我也是一天刷完就觉得完事大吉,后来发现过了一周什么也不记得。从今年开始,我每晚结束前都会花10分钟把当天题目的考点画进一张小地图里。比如Day1的地图围绕“双指针”展开:相向型、同向型(滑动窗口)、快慢型三个分支,每个分支下面挂我今天做过的题号和考察点。这样不是记单个题,而是记一个网络,做题时按网络索引,提取速度会快很多。
这张知识图谱不需要美观,我用的是随手笔记本,只写关键词和连线。重点在绘制的过程里,大脑会强制对题目做分类和归纳,这本身就是一种记忆强化。比如我画Day2的地图时,发现“二叉树的所有路径”“路径总和”这类题看似不同,其实都是前序递归加参数传递的变体,于是把它们归纳在同一个分支下。下次遇到“路径总和II”就知道该往这个方向想。
5.2 题型的迁移与举一反三
刷题到一定量以后,最宝贵的能力就是“看到一个题目,想到另一个会做的题目”。这次三天里我遇到好几次题型迁移:
- Day1的“无重复字符的最长子串”,迁移到Day1的“最小覆盖子串”,窗口收缩条件从“有重复”变成“覆盖全”,本质只是哈希表统计要求变了。
- Day2的“二叉树层序遍历”,加一个“按层翻转”或“只输出最右侧节点”,就变成了不同的题,但BFS框架完全不变。
- Day3的“打家劫舍”和“最长递增子序列”,前者是选或不选的状态决策,后者是枚举前一个位置进行转移,核心都是“每一步决策依赖前一步结果”的DP模型。
我会在记录表的“核心收获”字段里专门写一句“这题的XX思想等于Day1的XX题”,把跨题连接显式记下来。三天下来,这个连接网络越来越密,做题时的选择就不再是碰运气,而是有据可循的迁移。
5.3 把解法提炼成自己的模板
每做完一批同类型的题,我会把共同骨架抽出来,用自己能理解的语言沉淀成模板。模板的作用不是背答案,而是提供思考的起点。比如滑动窗口的骨架、BFS的骨架、DP二维数组的转移骨架,都是这三天沉淀下来的成果。
写模板时有一个潜在陷阱:不要直接抄网上的总结,一定要自己从做过的题里总结,哪怕一开始总结得很笨拙。自己生成的模板理解得深,遇到变体时才知道哪个部分可以改、哪个部分是固定的。比如滑动窗口里“右指针固定,左指针条件收缩”这个骨架,我根据自己做题中卡住的地方补充了“什么时候更新答案最省事”这张更细的地图,最终这套模板短小精悍,基本够用。
6. 常见问题与排查技巧实录
6.1 判题超时的排查思路
我这三天的21道题里,有过3次超时记录。每次超时我都按下面这个顺序排查:
- 先确认复杂度。如果复杂度本身是O(n²)而n已经到10^5,那换算法是唯一出路;
- 再排查常数优化。有些题复杂度正确但常数太大,比如频繁创建新数组、不必要的对象拷贝、哈希表用得不恰当,能改成局部变量和基础数组就改;
- 最后检查是否陷入死循环。滑动窗口题目超时很可能不是复杂度问题,而是while左移条件写反,导致指针不动,无限循环。
我印象最深的是Day1的一道滑动窗口变体,答案始终不正确且运行超时。断开调试后发现左指针在每次迭代开始时被重新赋值成了初始值,导致窗口永远没缩小。这类问题光看根本发现不了,必须借助在小样本上加打印、观察指针的移动轨迹来定位。有条件的话,把判题失败的用例复制到本地,用十几行的样例结合print看过程,很快就水落石出。
6.2 思路对但代码答案是错的
这个情况几乎人人都有。我的经验是:先把代码和伪代码逐行对照,看逻辑在翻译成语法时是否出现偏差;再看初始化值是否合理,比如记录最大值的变量初始化为0还是负无穷、最小值的变量初始化为正无穷还是数组首元素,差一个符号结果完全两样;最后检查索引上下界,比如循环里right的范围到底是len(s)还是len(s)-1,dp数组要不要开成n+1。
Day3的“编辑距离”我就在初始化上摔过跟头。dp[0][j]应该表示“从空字符串变到j字符需要插入j次”,结果我一不小心初始化为0,导致整个转移表建立错误,以后的答案全部偏低。这种错是最气人的,题目你懂、思路也对,就挂在基础定义上。后来解决方案是在白板上把小规模表格手工推一遍,亲手填出前两行两列,初始化问题就直接暴露了。
6.3 三天后坚持不下去怎么办
很多人不是能力不够,而是做不到“持续”。我自己三天的节奏能坚持下来,靠的不全是意志力,而是一个简单的机制:每天的目标在头一天晚上睡前就定好,精确到题号和难度,第二天按清单执行就可以。执行时不需要再花时间做决策,心理负担骤减。
如果某天实在状态差到完全不想刷,我会采取“保底策略”:只做一道最简单的题,5分钟解决,然后填记录表。这个动作虽然微不足道,但保持了“连续”的感觉,避免出现断档。刷题最怕的不是某天少刷,而是断掉后再也没有动力捡起来。记录表上连续三天的勾,本身就是很好的正反馈。
三天刷完不是终点,我通常会休息半天,然后把错题全部重新提交一遍,再挑一道同类但没做过的题快速检验。如果你也打算按这个模式来一次三天专项,记得核心不是“刷完21道”,而是“刷完还能说出每类题型的骨架、常见坑和可迁移的解法”。
我自己这个账号hetao1733837里其实存着一整条漫长的记录线,过去都是从某一天开始、在某一天断掉。这三天之所以值得写出来,是因为它终于不再是随机碰运气式刷题,而是形成了从选题到记录再到回顾的完整闭环。根据我个人的体会,刷题记录最大的意义,是让未来的自己不再害怕相同的坑踩第二次。