从盲目刷题到形成节奏感,大概每个认真打卡过 LeetCode 的人都经历过这样的转变。我自己的第十天就是一个很有意思的节点,既不像前几天那样靠新鲜感硬撑,也还没到真正得心应手的阶段,而是恰好处于“开始看懂题目套路”和“意识到自己基础薄弱”之间的尴尬地带。如果你正在坚持每天刷题,或者正准备开始,这篇文章想和你聊聊:第 10 天到底在练什么、怎么练才不白费,以及我踩过的那些完全可以避开的坑。
1. 第 10 天的真实位置:练的不是题目,是识别题目的能力
1.1 为什么前 10 天最容易放弃
很多刷题计划撑不过十天,不是因为题目太难,而是因为前期目标定得太高、反馈来得太慢。第 1 天你可能会兴致勃勃刷三题,第 3 天遇到二叉树递归开始卡壳,第 5 天感觉自己啥也没记住,第 7 天开始怀疑“算法岗是不是不适合我”。这种情绪波动非常正常,因为前两周本来就是学习曲线最陡、收益最不明显的阶段,它考的不是智商,而是你能不能把“刷题”从一个动作变成一套节奏。
我自己的建议是:前 10 天不要把目标定为“刷够多少题”,而应该定为“建立三种感觉”。第一种是熟悉常见数据结构的操作方式;第二种是识别题目背后考点的感觉;第三种是接受自己“看完题解需要复盘两遍”的耐心。这三种感觉一旦建立起来,后续刷题效率会明显比每天硬啃难题高很多。
1.2 第 10 天应该具备的能力基线
按照比较合理的进度,十天后你应该能做到这样几件事:看到一道题能大致判断它属于数组、字符串、链表、栈队列、二叉树、哈希表、双指针、前缀和、简单动态规划里的哪一类;遇到不熟的题目至少知道应该去哪里找思路;能在三十分钟内独立做出一道普通中等题,如果超过四十分钟还毫无头绪就主动看题解并复盘。
这个标准其实不算低,但它比“每天必须 AC 三题”要现实得多。因为刷题第 10 天真正想区分开的不是天赋异禀和普通人,而是“有没有形成自己的做题框架”和“永远在零散记忆题型模板”这两类人。我见过不少同学刷到 50 题时开始出现一个明显分水岭:一部分人开始觉得“很多题其实是一种思路的变体”,另一部分人还是在靠题感硬碰。差别就是从第十天前后累积起来的。
1.3 一个好用的节奏模板
我这里分享一个我实际用了一段时间的模板,它不见得适用于每个人,但至少能帮你脱离“今天不知道做啥题”的状态。晚上安排一个固定的时间段,先复习昨天的错题,再做一道新题,最后留十五分钟整理笔记。一周下来,你会形成一个周期:读题、尝试、卡住、看题解、理解、复现代码、隔天重做。到了第 10 天,你会明显发现自己看题的方式从“凭感觉”变成了“看约束条件”。
时间上我比较推荐晚饭后固定的时间段,比早上刷更有效。因为刷题需要一个相对安静且能持续专注的场景,早上的零碎时间适合背一些常用模板,晚上的整块时间更适合做完整推理和代码实现。如果你已经工作了,中午休息时间也别浪费,可以拿来看一道题的分析思路,不用写代码,只求理解。
2. 第 10 天从哪几类题里选料:最容易被低估的高频考点
2.1 动态规划和滑动窗口为什么值得提前碰
很多新手刷题会本能避开动态规划和滑动窗口,觉得它们难、技巧性强、容易打击信心。但从实际面试和竞赛的角度看,这两个类型往往是中等题里出镜率很高的套路,越早建立认知反而越有优势。第 10 天不需要你快速写出转移方程,但至少要能看懂一个简单的动态规划题的标准写法。
我举个例子,经典的爬楼梯问题,问你到第 n 级台阶有几种走法,每次可以走一级或者两级。很多第一次接触的人会愣住,但如果告诉你这就是从斐波那契数列延伸来的,你会立刻觉得原来这么简单。所谓动态规划,核心就是找状态、找转移、找边界条件。在这个例子里,状态是 dp[i] 表示到第 i 级台阶有多少种方法,转移是 dp[i] = dp[i-1] + dp[i-2],边界是 dp[1]=1、dp[2]=2。代码写出来也就五六行,这才是入门级的动态规划应该有的样子。
2.2 滑动窗口的精神内核:不是“数数”而是“维护”
滑动窗口它本质上是一种优化的双指针思路,核心在于维护一个窗口,让它始终满足题目要求的条件,并在这个过程中统计答案。比如说“找出字符串中不含重复字符的最长子串”,暴力解法是枚举所有子串,然后判断是否有重复字符,复杂度能到 O(n^2),字符串一长就非常痛苦。用滑动窗口的话,我们会用两个指针 left 和 right 维护一个窗口,再用一个哈希表记录窗口里每个字符出现的位置,一旦发现重复字符就移动 left 指针跳过。这样每个字符最多进出窗口一次,整体时间复杂度降到 O(n)。
第十天你完全可以开始接触这种思路,因为它的代码量很小,逻辑也不复杂,真正考验人的是你能不能识别出“这是一道滑动窗口的题”。一旦你认清这点,后面遇到类似的问题基本上就是换壳子。这也是为什么我不建议刷题只按数据结构的顺序推进,而应该穿插着接触这些算法范式。
2.3 双指针与链表:会写不难,难得是防错
双指针也是前期很值得练的板块,尤其是和链表相关的题目。链表的题目往往代码不长,但是容易在边界条件上翻车,比如空链表、只有一个节点、删除头节点等特殊场景。我自己在第十天前后做过一道“反转链表”的题,看着很简单,递归版几行代码就结束了,迭代版稍微长一点。真正让我记住的不是解法本身,而是递归调用返回后指针怎么指、边界条件怎么处理,这些细节如果不去手推一遍,很容易看完题解觉得自己懂了,合上书自己写却写不出来。
链表题目的价值就在这:它逼着你去画图、去模拟每一步的指针指向,这种基本功在做后面的复杂题时非常有用。如果你发现自己写链表题老是空指针异常,不用沮丧,这其实是个好信号,说明你已经意识到“代码里的每一个变量都不能想当然”这个道理了。
2.4 哈希表与前缀和:空间换时间的思路启蒙
前十天最容易建立成就感的数据结构是哈希表,因为很多“需要快速查找”的问题都能靠它优化。比如两数之和这道经典题,暴力做法是双重循环,复杂度 O(n^2);用哈希表记录已经遍历过的数字和它的下标之后,每遍历一个新数字只需在哈希表里查找“目标和减去当前数字”是否存在,复杂度直接降到 O(n)。这是“用空间换时间”最有代表性的例子之一,也是很多人第一次感受到算法优化魅力的地方。
前缀和这个技巧,我个人建议在第 10 天的时候至少要理解概念,不急着大量刷题。因为很多前缀和的题目会和哈希表、二分查找结合,新手容易一头雾水。但如果你先理解了“前缀和数组 pre[i] 表示从第一个元素到第 i 个元素的和”,那“任意区间的和等于 pre[right] - pre[left-1]”这个公式随便就能推出来,后面遇到统计连续子数组个数的题目就有了基础。
3. 实操过程复盘:我第 10 天做的一道典型题及完整思路
3.1 题目选择与输入场景
我第 10 天晚上刷的是一道中等难度的数组题,具体描述是:给定一个整数数组 nums 和一个整数 target,要求你返回所有和为 target 的连续非空子数组的个数。题目的约束条件是数组长度最大可能到 10^5,元素的范围包括正数和负数。这意味着你不能用枚举所有子数组的暴力方法,因为数量级是 O(n^2),在 10^5 的量级下根本不现实。
为什么要挑这道题来复盘,因为它正好综合了两种第 10 天应该接触的核心思路:前缀和与哈希表。而且它有个很隐蔽的坑点,就是元素里有负数,这会让“滑动窗口”的经典解法直接失效。很多人一看到“连续子数组的和等于 target”就条件反射地想去用滑动窗口,在全是正数的场景下确实可行,但元素有负数时,窗口收缩的判断条件就不成立了,这绝对是前期刷题时会真实踩到的坑。
3.2 思路推导过程
当时我看完题目后没有直接写代码,而是先做了一次比较规范的思路推导。核心公式就是:如果一个子数组 nums[j..i] 的和等于 target,那么 pre[i] - pre[j-1] 一定要等于 target。稍微变一下形就是 pre[j-1] = pre[i] - target。
这意味着我可以一边遍历数组,一边维护当前的前缀和 pre,同时用一个哈希表记录已经出现过的所有前缀和的值以及它们出现的次数。每到一个位置 i,我只要查一下哈希表里有多少次出现了 pre - target,这个数量就是以当前位置结尾的、满足条件的子数组个数。然后把 pre 本身也加进哈希表里,继续看下一个位置。
这个过程里有个细节要非常注意:不是把 pre[i] 全部算出来再统计,而是一边遍历一边统计。为什么要这样?因为统计的是以当前位置结尾的子数组,前缀和 pre[j-1] 必须是在 i 之前的某个位置产生,不能把自己和自己的前缀和拿来配对,否则会出现一个长度为 0 的子数组被错误计入的情况。我一开始没多想,直接先构建前缀和数组再用哈希表统计,就多算了好几个空子数组,排查半天才反应过来。
3.3 代码实现与关键注释
from collections import defaultdict def subarraySum(nums, target): # 哈希表记录前缀和出现的次数 # 这里初始放入 {0: 1} 是一个非常关键的设计 prefix_count = defaultdict(int) prefix_count[0] = 1 current_sum = 0 result = 0 for num in nums: # 更新当前前缀和 current_sum += num # 如果之前出现过 current_sum - target 这个前缀和 # 说明从那个位置之后到当前位置的子数组和为 target result += prefix_count[current_sum - target] # 将当前前缀和加入哈希表,供后续统计使用 prefix_count[current_sum] += 1 return result这段代码里最重要的就是prefix_count[0] = 1这一句。它看起来不起眼,但如果你漏掉了,那么当current_sum - target正好等于 0 时,就会少统计一种合法情况,直接导致测试样例不过。我以前在这里栽过跟头,所以想专门提醒一下:有些边界值不是靠测试用例提示的,而是靠推导边界时需要自己意识到的。
3.4 从这题里提炼出来的通用做题方法
做完这道题之后我没有马上刷下一题,而是花了二十分钟做了一组总结。我发现自己从这道题里提炼出了一种“思考连续子数组问题的通用套路”:看到连续子数组相关的问题,第一反应先去想能用哪种数据结构维护某种累积信息;如果数组里元素有负数,滑动窗口大概率不适用;看到区间和的需求,优先考虑前缀和;看到“统计出现次数”之类的问题,优先考虑哈希表。
这种总结能力,恰恰就是第 10 天刷题最该积累的东西。你现在觉得每天刷到的东西是零散的,但如果你每道题都做一次这样的提炼,刷到第 30 天的时候,你会发现自己看到新题的思维方式已经完全不同了。
4. 调试技巧与常见报错排查实录
4.1 程序“答案错误”比“运行超时”更难搞
第 10 天刷题阶段,最容易遇到的报错就是答案错误,也就是 Wrong Answer。这时候千万不要急着抓瞎改代码,也别直接翻评论区看别人怎么说。正确的做法是先自己构造几个测试样例,看问题出在哪一步。一般来说有三个检查方向:一是看自己的算法思路有没有漏洞,比如刚才说的负数场景下滑动窗口失效;二是看边界条件有没有特殊处理,比如数组为空、目标值为 0、所有元素都相同;三是看看会不会是多算或者漏算了某些情况,拿前缀和的题来说就是空子数组有没有被算进去。
运行超时则是另一种情况,它往往提示你的算法复杂度不达标,而不是代码写错了。超时的时候要算一下你的暴力解法在极限数据规模下的操作次数,如果上了亿级那么基本就会超时,必须考虑优化。第 10 天接触这些概念可能会觉得有点抽象,但一定要养成估算复杂度的习惯。
4.2 一些看起来很奇怪的报错信息
除了常见的答案错误和超时,还有一些新手很容易碰到的情况。比如数组越界,尤其在做链表题或矩阵题时很常见;比如整型溢出,虽然刷题时语言和平台通常会帮你处理,但有些语言里整数相乘还是可能溢出;还有哈希表操作时的 key 不存在,如果你用dict.get()而不是直接索引访问,就能避免 KeyError。
我在第 10 天前后处理这类报错的方法是:先看报错信息提示的是哪一个测试用例,再单独用这个用例在本地跑一遍,配合输出中间变量的语句,一点点定位问题。写完代码之后我会自己手动过一遍几个关键的小例子,比如[1,1,1], target=2、[1,-1,0], target=0这类极端边界,确认逻辑没问题再提交。
4.3 一个只属于初学者的时间管理问题
排查报错本身是刷题的一部分,但你也要警惕陷入“死磕”的状态。如果一道题想了四十分钟仍然没有任何头绪,那就应该果断看题解了,不是认输,而是在节省时间,把时间花在理解和复盘上更有价值。我这里说的理解,不是看完别人代码觉得自己懂了,而是把别人的思路合上,自己默写出代码并解释每一步是为什么。第 10 天是培养这个习惯的最好时机。
4.4 前十天常见问题速查表
| 高频问题 | 可能的原因 | 排查方法 |
|---|---|---|
| Wrong Answer | 算法思路有漏洞 | 构造边界样例,逐段打印中间值 |
| Time Limit Exceeded | 时间复杂度不达标 | 估算暴力解法操作次数,考虑优化数据结构 |
| 空指针异常 | 链表/树节点未判空 | 在访问节点属性前检查节点是否为 null |
| KeyError | 哈希表访问不存在的键 | 使用 get 方法或者先判断键是否存在 |
| 变量名写错 | 复制代码时遗留 | 检查每个变量是否在循环内外都被正确初始化 |
这张表看着简单,但你每踩过一个坑,就给它加一行自己的备注,积累到后面会成为相当有价值的个性化刷题档案。
5. 复盘笔记体系:让第 10 天成为高效刷题的真正起点
5.1 笔记分类与记录逻辑
刷到第 10 天,你的手机相册、浏览器收藏夹或本地 txt 文件里可能已经存了不少题解截图和代码片段。但如果这些内容只是躺在收藏夹里吃灰,那刷题效果会大打折扣。我比较建议从第 10 天开始建立一个简单的复盘笔记体系,它不需要多花哨,用本地文档就行。每道题记录三样东西:考点标签、我自己的错误、这道题值得记住的一个核心技巧。
以我第五到十天刷的一批题为例,记录类似这样:数组-前缀和-哈希表,错误点是在有负数的情况下用了滑动窗口,核心技巧是下标映射避免二次遍历;链表-快慢指针,错误点是没有单独处理链表中只有两个节点的情况,核心技巧是循环终止条件的判断技巧。
这样的笔记在一开始可能会觉得多余,尤其是每天刷题已经很累了,还要额外花时间写总结。我自己的经验是,笔记不需要长,每道题两三行就够。关键是“考点标签”和“错误点”这两栏,它们会在你后续复习的时候起到检索目录的作用。
5.2 穿插复习策略
讲到复习,就不得不提到一个很常见的问题:今天刷的题,明天再写一遍还是不会。这不奇怪,因为短期记忆的遗忘速度本来就很惊人。我的策略是做“间隔复习”,简单来说就是在第 1 天、第 3 天、第 7 天、第 14 天这四个时间点,重写一遍当时的错题或经典题。如果每次都能不卡壳地写出来,就可以把这个题从复习队列里划掉;如果还是卡壳,就再写一遍总结。
这个方法对第 10 天的你来说可能有点超前,因为系统性的复习计划往往要到刷题量累积到三四十题以后才有明显的需求。但你现在就可以养成一个习惯:每天做新题之前,先花十分钟重写一道昨天的题。这个动作能让你保持对旧知识的温度,也减少后面集中复习时的崩溃感。
5.3 放弃“量”的执念
最后想说的是,第 10 天你已经可以开始有意识地控制刷题数量了。每天刷三题但一题都没消化透,不如每天认真磨一题值得。我说的“消化透”指的是你能不看任何提示写出来,并且能解释清楚每个细节的用途。如果一道题你能做到这个程度,它给你带来的提升远远超过草草刷完五道题。
从我的观察看,那些能坚持到第 30 天、第 50 天的人,普遍不是刷题最多的,而是复盘最认真的。第 10 天刷多少题根本不重要,重要的是你有没有建立“刷题、记录、复盘、重做”的闭环。
我自己刷到第十天的时候,最大的一个感受就是:LeetCode 上的题,本质上是把你往“工程师思维”方向训练。它教你的不只是几个算法,而是如何在约束条件下找可行解、如何在效率与实现复杂度之间做取舍、如何在面对未知问题时拆解成已知问题。这种思维方式会慢慢渗透到平时的代码设计、组件封装、甚至排查问题的思路里。如果你能看到这层价值,每天花在刷题上的时间其实非常值得。