如果你在准备算法面试,或者正在刷题,但总觉得进度慢、方向乱、刷了就忘,那这篇内容应该能帮到你。我按自己的节奏刷完了LeetCode“面试经典150”这套题单,用93天完成一轮系统性的复盘和整理,这篇文章把我在这个过程中的路线规划、题目拆解、踩坑心得和面试前的冲刺方法都写出来了,不需要你完全照搬,但很多思路可以直接拿来用。
1. 为什么选择“面试经典150”而不是盲目刷题
市面上的题单很多,热题100、剑指Offer、各种付费专栏的题目合集,甚至不少博主自己整理的高频题清单。我最早也经历过“打开LeetCode不知道点哪题”的阶段,今天做一道链表、明天做一道动态规划,后天又跳到图论,表面上每天也在刷,但三个月过去回头一看,知识体系是碎的,面试里稍微换个问法就卡壳。
后来我才意识到问题不在“刷得不够”,而在“没有结构”。面试官考察的算法能力,本质上是一套可枚举的题型体系:链表操作、二叉树遍历、双指针、滑动窗口、动态规划、贪心、图论、回溯、堆/栈/队列、排序与搜索,翻来覆去就这些门类。LeetCode官方出的“面试经典150”恰好是按这套体系组织的一份题单,150道题,覆盖面比热题100更完整,难度梯度也更接近真实面试,不是一上来就甩一堆Hard把人劝退,而是从Easy到Medium到Hard逐层递进。
我选这套题单有几个很现实的原因。第一,它是免费的,不用买会员,从LeetCode首页进去就能找到“面试经典150”的入口,所有题目都对普通用户开放。第二,它按主题分好了15个大类,不需要自己去归纳题目类型,跟着题单走,每天知道自己该练什么,这样天然就解决了“不知道刷什么”的问题。第三,它的题目来源有明显的高频属性,很多题在面试里属于“原题重现”级别的出现率,比如两数之和、最长回文子串、二叉树层序遍历这些,我不止一次在模拟面试和真实面试中碰到同类型题。
另外我要说一点可能会被很多人忽略的:刷题数量永远不是目的,题型覆盖率才是。“面试经典150”的价值不在于“150”这个数字,而在于它试图用150道题去覆盖算法面试的绝大部分基础模型。你把这个模型吃透了,遇到新题只是在已知模型上做变种,而不是重新发明解法。
2. 93天刷完150道题的路线规划和节奏控制
2.1 三轮推进,而不是线性刷完
真正执行的时候,我没有按题单顺序从头做到尾,而是用了三轮推进的策略。
第一轮花30天,做的是“题型扫盲”。目标不是每道题都做对,而是把所有题型都过一遍,知道每一类题长什么样、最经典的解法是什么。比如动态规划大类下,背包问题、子序列问题、区间DP、状态机DP我都至少接触一道;图论大类下,DFS、BFS、拓扑排序、并查集各练几道。这一轮我做错了很多题,但我不害怕错,因为我的目标就是建立“题型地图”。
第二轮花40天,做的是“按专题深挖”。这轮我会按大类集中突破,比如连续五天只做链表题,把反转链表、删除节点、合并链表、环形链表全部吃透,做完之后自己写一页总结,把这一类题的解法和易错点列出来。这一轮是最辛苦但也最见效的,很多之前“好像见过但写不出来”的题,这轮做完之后基本能独立写出最优解。
第三轮花剩下的20多天,做的是“随机抽题和错题重刷”。看到这里你应该明白为什么选93天这个长度,因为这刚好够完成三轮,每一轮有明确的目标,节奏感很强。如果把150道题平均分配到93天里,每天不到2道题,看起来轻松,实际上没有复习、没有归类、没有沉淀,刷到最后前面的早忘了。三轮推进虽然同一道题可能被刷两三遍,但每一遍的侧重点不同,记忆效果远好于一次性刷过。
2.2 每天投入多长的时间才是合理的
我身边的同学经常问我:一天刷几道题合适?我的答案是:不要用“道”来计算,要用“小时”来计算。如果你一天能全身心投入2小时,那就按2小时来做规划;如果能投入3小时,就按3小时规划。
我在第30天到第60天这段深挖期,每天基本是3小时起步,我给自己定的规则很简单:
- 1小时做新题,控制在1-2道,禁止贪多;
- 1小时复盘昨天的题,不看题解,尝试自己重新写出来;
- 1小时整理笔记和总结模板,把同类型的解题框架提炼出来。
到了第三轮冲刺期,每天的小时数可以压缩到2小时,因为大部分题已经熟悉了,重点只放在错题和变形题上。
这里有一个特别反直觉的经验要分享:慢就是快。你一天刷10道题,每道题都是看一眼题解然后抄一遍代码,看起来进度很猛,实际上什么也没留下。你一天踏踏实实消化1道题,能独立推导出解法、能说出为什么这么做、能把代码写对,看起来慢,但30天后你会发现这类题你已经彻底掌握了。我第一轮就吃过贪快的亏,后来每次准备图个“今天刷了8道”的虚荣心,结果自测的时候连基础题都写不出来。
2.3 用表格管理进度而不是靠脑子记
150道题,如果用脑子记哪些做完了、哪些没做完、哪些做错了,一定记不住。我从第1天开始就用一个表格来管理,字段很简单:题号、题名、所属专题、难度、第一轮状态、第二轮状态、第三轮状态、备注。
这里的“状态”我用三个值:未做、AC(Accepted,独立写出通过)、需要复习。备注里写这道题的关键解法或者踩坑点。表格的好处是,它能让你直观地看到自己的薄弱环节在哪里。比如我统计之后发现我的“图论”一栏几乎全是“需要复习”,那下一轮我就知道要加练图论,而不是闭着眼睛随机刷题。
说实话,这个表格用Excel都行,没有必要搞特别复杂的工具,重要的是记录本身。我在Day30、Day60、Day90三个节点都会统计一遍各专题的AC率,算一下每个大类的完成度和薄弱点,然后调整下一轮的侧重点。这样做下来,你每天刷题都是“冲着短板去”的,而不是漫无目的地刷。
3. 核心实战:高频题型的解题思路和踩坑点
“面试经典150”里有些题目几乎可以称为面试的“必考题”,出现的频率高到离谱。我在刷题过程中特意对几道经典题做了深度整理,下面挑几类最有代表性的,把思路和坑点拆开讲。
3.1 翻转链表类:看似基础但细节极多
链表题是面试里的入门级送分题,但很多人都在送分题上翻车。比如第206题“反转链表”,思路很简单:用三个指针prev、curr、next,每次把curr.next指向prev,然后整体后移。代码量不到十行,但很多初学者写出来之后指针用得一团乱,要么丢节点,要么成环。
我总结的检查方法是:写完之后问自己三个问题。第一,头节点的next最后是否为null;第二,原链表的尾节点是否变成了新链表的头;第三,当链表为空或只有一个节点时,代码是否正确。这三个问题都答上来,这道题才算真正过了。
比基础反转稍微进阶一点的是“K个一组翻转链表”(第25题),这题在面试中出现的频率相当高。它考察的不只是指针操作,还有“递归+迭代”的组合能力。我当时的做法是把链表按K个一组切割,对每一组子链表调用普通的反转函数,然后递归处理剩余部分。切的时候要注意保存下一组的头节点,不然递归进去就找不回来了。第一次写这个题的时候,我调试指针调试了一个多小时,后来我明白了一个道理:链表题一定要画图,在草稿纸上把前后节点的关系画清楚,再开始写代码,不要边想边写。
3.2 滑动窗口与双指针:吃透一个模板走天下
“面试经典150”里有一大批字符串和数组题,都可以用滑动窗口来解决。最典型的比如“无重复字符的最长子串”(第3题),这几乎是面试中出现率最高的字符串题之一了。
滑动窗口的模板其实是固定的:用两个指针left和right维护一个窗口,右指针不断右移加入新字符,同时用哈希表记录窗口内字符的状态,遇到不满足条件时左指针右移收缩窗口。写多了你会发现,什么“最小覆盖子串”、“字符串排列”、“最长重复字符替换”,本质上都是这个模板的变种。
很多人在滑窗题上踩的坑是:边界条件搞不清左指针什么时候移动、移动多少。我的经验是,不要背模板,要背“窗口的语义”。先把窗口定义清楚,比如“以right为结尾的最长无重复子串的起始位置是left”,然后自然就能推导出更新逻辑。我在刷了七八道滑窗题之后,自己动手总结了一份“滑窗四步法”:先定义窗口含义,再定扩展时机,再定收缩条件,最后定答案更新位置。照着这个四步走,新题基本能很快拆解出来。
另外,双指针还有一种常见场景是“有序数组去重”、“移除元素”这类原地修改题。这类题的思路是维护一个慢指针,快指针在前面探路,当满足条件时,把快指针指向的值赋给慢指针的下一个位置。要特别注意的是“到底是先移动指针再赋值,还是先赋值再移动”,这类细节只有自己手写一遍才记得住,所以我建议这类题别光看题解,一定要亲手敲一遍。
3.3 爱吃香蕉的狒狒:二分答案的经典模型
在第73个学习日附近,我刷到一道印象特别深的题——LeetCode 875题,就是热搜词里提到的“爱吃香蕉的狒狒”。外卖这题看起来像个模拟题,实际上考的是“二分答案”这个非常经典的算法模型。
题目背景是:有一堆香蕉,分成了若干堆,你每小时能吃K根香蕉,但一小时只能吃同一堆里的香蕉,如果一堆不够K根,吃完这一堆就等下一个小时。题目要求找到一个最小的K,使得能在H小时内吃完所有香蕉。
我第一次看到这题的时候,第一反应是能不能模拟?后来发现不行,堆数和H的范围都很大,直接模拟肯定超时。正确的解法是把问题转换成“判定问题”:给定一个K,能不能在H小时内吃完?这个判定函数很好写,就是遍历每一堆香蕉,累加需要的耗时ceil(pile / K)。然后对K进行二分查找,下界是1,上界是最大堆的香蕉数。
二分查找的边界处理是这个题最容易出错的地方。我的习惯是:while (left < right) 的循环条件,配合 mid = (left + right) >>> 1 这种取中间值的方式,判断 if (canFinish(mid)) 时 right = mid,否则 left = mid + 1。这样写的好处是不会出现无限循环,而且最终left就是答案。刚开始写二分的同学经常在 left、right、mid 的赋值上晕头转向,我建议多拿几个用例跑一跑,比如 H 刚好等于堆数、K 取到最大值这些边界情况,能帮你很快理解。
这道题代表了一类非常重要的题型——“二分搜索答案”。很多面试题表面上问“最小化最大值”或者“最大化最小值”,实际上都可以转换成这类二分答案加判定的模式,比如“分割数组的最大值”、“每个孩子最多能分到的饼干数”等。吃透875题带给我的不是一道题,而是一整个题型的钥匙。
3.4 二叉树遍历:迭代写法是面试的分水岭
二叉树相关的题目在“面试经典150”里占了相当大一部分。很多人递归写法写得很溜,但一要求用迭代写就卡壳了。这其实是面试的重要考察点,因为递归虽然简洁,但不熟练的候选人可能在递归深入时犯迷糊,而迭代写法能真实反映对栈和队列数据结构的理解程度。
前序遍历和中序遍历的迭代写法,是必须要掌握的。前序的思路是:用一个栈,先压入根节点,弹出一个节点时记录值,然后先压右孩子再压左孩子(因为栈是后进先出,所以要左先出就得右先入栈)。中序的思路稍微绕一点:一路向左压栈,走到头之后弹栈记录,再把指针移到右孩子继续。层序遍历则是用队列,每次处理一层的节点数量,用循环输出这一层,这在“二叉树的右视图”、“层序遍历”这些题目里都是核心模板。
二叉树题有一个通用自查方法:画一棵三层满二叉树,把你的代码跑一遍,看输出是否符合预期。大多数错误都能通过这种方式查出来。尤其是涉及到递归的时候,一定要搞清楚“返回值是什么”和“递归出口在哪里”,这两个问题模糊的话,代码跑起来会非常痛苦。
3.5 动态规划:状态定义是灵魂
动态规划是算法面试里最难啃的硬骨头,也是“面试经典150”里题目密度最高的大类之一。我在第一轮扫盲的时候,动态规划几乎让我怀疑人生,每道题看完题解都懂了,合上题解又写不出来。后来我发现问题出在:我总是试图“背转移方程”,而不是真正理解“状态定义”。
以“打家劫舍”为例:你是一个小偷,不能偷相邻的两家,怎么偷到的钱最多?大多数人直接能记住转移方程 dp[i] = max(dp[i-1], dp[i-2] + nums[i]),但如果你不明白 dp[i] 表示的是“从第0家偷到第i家能获得的最大金额”,那稍微换个条件,比如“环形街区首尾相连”,你就又不会了。
我后来的方法是:拿到一道DP题,先不急着列转移方程,先用一句话把状态定义写清楚,写在草稿纸上。比如“dp[i][j]表示s1的前i个字符和s2的前j个字符能否交错组成s3的前i+j个字符”。状态定义对了,转移方程一定能推导出来,因为转移就是在枚举“最后一个动作是什么”。
另一个非常重要的DP技巧是:从小规模例子开始手动模拟。很多DP题,比如“编辑距离”、“最长公共子序列”,把两个小字符串手动填一遍表格,等于把整个解法亲手推演了一遍,比看十遍题解都有用。我强烈建议每个DP题都要手动填表一次,填完你的理解会深入一个台阶。
4. 刷题过程中的常见问题与排查技巧
93天刷题过程中,我遇到过的坑绝对不算少。下面这些问题是我自己踩过的,也是我问了很多一起刷题的同学之后总结出来的高频问题,如果你们也遇到了,不妨按我的排查思路试一下。
4.1 为什么看了题解感觉懂了,但合上就写不出来
这应该是刷题人最常遇到的挫败时刻。我的排查结论是:你不是“没学会”,而是“没有建立主动回忆的路径”。只看题解而不动手,属于被动输入,大脑不会认真对待;只有逼自己不看题解写一遍,当时写不出来也没关系,卡住的地方就是你的薄弱点,记下来,过两天再重写一次,直到能独立写完为止。
我把这个方法叫作“三遍法”:第一遍看题解完成后理解并默写;第二遍隔一天后凭记忆重写;第三遍隔三天后独立写出并提交AC。三遍走下来,这道题才真正属于你。如果只做一遍,最多七天后就会忘得干干净净。
4.2 刷到一半想放弃,怎么对抗倦怠期
第40天到第60天这段时间,是我最想放弃的阶段。新题难度上来了,旧题又开始遗忘,模拟面试时发挥不稳,加上身边总有人说“刷题没用,不如去背八股”。但后来我的调整方法是:把“目标感”拉到最长线。
我给自己定的规矩是:每天无论多忙多累,至少提交一道题,哪怕是一道简单题。这个习惯的目的是维持连续性,因为中断三天以上,重新拾起来的成本会成倍增加。同时我砍掉了“必须每题都想出最优解才能提交”的心理包袱,先把题目搞懂、提交过、AC了,就已经是胜利。
另外,我会每隔半个月翻一次“已经AC的题目列表”,看到绿色的一大片的时候,成就感是真的能充电的。这种及时反馈很重要,刷题本来就是长跑,你得学会给自己制造正反馈。
4.3 刷题速度和正确率怎么平衡
很多人喜欢“刷完一道是一道”,不AC就不看下一题。但我的经验是:一道题如果独立思考了45分钟以上还是完全没有思路,就不要再硬扛了。直接看题解,看懂后合上题解自己写,把这道题标记为“需要复习”。
这不是认输,而是效率管理。面试中遇到新题,你也不可能每题都自己做出来,更多的场景是“见过类似题,能快速套上模型”。那怎么增加“见过”的题量?只能通过多接触题目来达成。45分钟是黄金阈值,超过45分钟边际收益急剧下降,这时候看题解反而是在节省时间。
4.4 面试前几天到底该刷什么题
面试前一周,不建议再开新题了。我自己的做法是:把“面试经典150”里标记为“需要复习”的题全部重新过一遍,尤其是链表、二叉树、动态规划、滑窗这几类高频模块。然后每天做三到五道随机题,模拟机考环境,严格计时。
到了面试前一天,只做一件事:把每个专题自己总结的模板看一遍。比如反转链表怎么写、滑窗模板四步是什么、二分答案怎么判断边界。这些东西不需要现场推理,应该已经内化成肌肉记忆。如果面试前脑子里还能准确浮现这些框架,那你大概率能发挥出平时的水平。
5. 从刷题到面试:如何把题型套路转化成实战能力
5.1 学会“讲题”比学会“写题”更重要
这是一个很多人直到面试才发现的坑:你会写题,但不一定会把思路讲清楚。面试的流程通常是:面试官出题,你先说思路,他点头之后你再写代码。如果你思路表达得混乱,哪怕代码写对了,面试官对你的评价也可能大打折扣。
我从第60天开始,要求自己每做一道题就录一段语音或对着空气讲解一遍,内容包括三部分:题目在问什么、我的思路是什么(为什么这么做)、复杂度是多少。这样练了二十多天之后,再去面试,我明显感觉自己表达思路时顺畅多了。“这个题的核心矛盾是...,所以我要用...来处理,时间复杂度是...”,这句结构化的话术帮我顺利拿下了好几轮面试。
5.2 一定要做模拟面试
如果你也有“平时刷题很稳,一上考场手就抖”的问题,那模拟面试是你必须过的一道关口。线上有很多免费或者低成本的模拟面试平台,也完全可以找同学互相出题、互相点评。我的经验是,模拟面试至少要做三次以上,第一次一定会紧张到语无伦次,第三次之后就会明显好很多。
模拟面试时要注意还原真实环境:只给一个题面,限时做题,边说边写代码,不能中途查资料。每次模拟之后复盘,把所有卡壳的地方记录下来,下一次尝试改进。这个过程会很痛苦,但我可以很负责任地说,它的价值不亚于多刷50道题。
5.3 刷完150道题之后,怎么持续保持状态
如果你已经完成了“面试经典150”,恭喜你,你已经有了一张比较完备的算法题型地图。但刷完不等于结束,我建议你做三件事。第一,把150道题分成“熟练掌握”和“仍需复习”两个列表,优先处理后者。第二,不定时随机抽题,检验自己的长期记忆。第三,如果你的目标是大厂面试,可以再去做做LeetCode按公司整理的题库模块,把“面试经典150”里的模型运用到新题上。
我自己在刷完150之后又遇到的一个挑战是:很多题型明明掌握过,但过了一个月再写又生疏了。这很正常,算法本来就是一门需要持续接触的功夫。我现在的方法是在LeetCode上参加周赛,每周腾出一个半小时,跟成千上万的人一起做题。周赛的题目更新鲜、更贴近当前热门面试题的方向,也是保持手感的好方式。说到这个,我看最近的“LeetCode周赛430”很多人都参加了,这种固定高强度的计时训练,对检验和巩固自己的水平非常有帮助,哪怕排名不靠前,也能帮你定位出自己的短板。
6. 最后分享一点我个人的体会
93天刷完“面试经典150”,回头看最大的收获并不是那一页“已通过”的绿色记录,而是一整套我自己总结的、可复用的思考方式和分析框架。刷题真的不只是一个“背答案”的过程,它在训练你把一个复杂问题拆解成更小模块的能力,这种能力在各种技术实战里都非常有用。
如果在看完这篇文章后,你能记住三句话,我会很满足:第一,刷题要按题型体系推进,不要随机乱刷;第二,每一道题都要真正搞懂,反复复习,而不是只看数量和进度;第三,面试前要专门练“讲题”和“模拟”,刷题和面试是两回事。
最后再分享一个小技巧:我刷题时的每一页笔记上都会留一块空白区,专门用来记录“这道题让我涨了什么见识”。有些是“原来布尔数组可以当状态压缩用”,有些是“排序后双指针能大幅降低时间复杂度”,这些零碎的小认知,最终拼成了我对算法面试的完整理解。拿一套题单踏踏实实走完一遍,你会体会到那种从“看到新题心慌”到“看到新题觉得有趣”的变化,这个过程确实挺值的。