从第1天就开始刷这套题的人很多,能坚持到“day105”的并不多。3月6号这天,我刚好卡在100天刚过的节点上,把面试经典150题的进度条拉到接近尾声。回头看这三个月零几天的过程,最大的感受不是“题变简单了”,而是“会做题”和“会讲题”之间隔着很长一段路。这篇内容把我自己这105天怎么拆解150题、怎么定计划、怎么背模板、怎么避开原地踏步的坑都整理出来,给正在刷这套题的人一个参考。
先说明白这套150题是什么。它不是LeetCode全题库的随机抽取,而是按面试高频考点整理的一套经典题单,覆盖数组、字符串、链表、二叉树、回溯、动态规划、图论、堆、二分、滑动窗口、栈与队列等核心算法类型。很多人问“刷500题够不够”“要不要刷800题”,我的结论是:如果目标是备战国内外的算法面试,这150题足够打底,关键在于你用什么方式刷。
1. 先说结论:为什么我认准这套150题
1.1 题量不是关键,覆盖面才是
很多第一次接触算法面试的人都容易被题库总量吓住。LeetCode题量早就突破3000道,全刷是不现实的,也没必要。面试官出题的核心逻辑不是“考你没见过的题”,而是“在有限时间判断你的思维方式和编码习惯”。因此,高频面试题考点其实高度集中,翻来覆去就是那几十个题型。
这套150题的价值就在于它把高频考点收敛成一个可执行的清单。数组、字符串专项解决基础编码能力;双指针、滑动窗口覆盖大部分线性扫描问题;二叉树和递归是面试必考的“基本功”,必须拿满;动态规划和回溯是区分度最高的部分,是大多数候选人拉开差距的分水岭。覆盖到这些核心栈之后,再遇到什么变形题,基本都能套回某一个熟悉的考点框架。
1.2 面试的“常青树”属性:经典题会反复出现
我自己在模拟面试和实际面试中撞见过的题目,几乎都能在这150题里找到影子。不是原题,但核心模型完全一致。举几个简单的对应关系:反转链表、合并两个有序链表这类题目对应链表指针操作基本功;岛屿数量、被围绕的区域对应网格DFS/BFS的染色法;无重复字符的最长子串、长度最小的子数组对应滑动窗口的窗口维护逻辑。这些模型不夸张地说,是算法面试的“常青树”。
因此,一套150题认真过三遍,效果比盲目刷500道随机题好得多。第一遍求理解,第二遍求速度,第三遍求讲清楚。能做到这个程度,应对绝大多数面试场景完全够用。
2. day105复盘:我的150题计划是怎么拆的
2.1 整体进度:不追求一天五题,追求“天天不断”
我见过不少人刷题一开始热情极高,一天刷七八道,两周之后因为一道难题卡了两天,心态直接崩掉,然后整个计划搁浅。我自己采用的方式是“少量多餐、滚动复习”,每天确保3道题左右的节奏,状态好就多刷1道,状态差就只复习旧题,但绝不中断。
真实的时间线大概是:
- 第1天到第40天:按标签顺序过数组、字符串、双指针、滑动窗口、哈希、栈。
- 第41天到第80天:主攻链表、二叉树、二分查找、回溯。这段时间是最容易产生挫败感的,因为二叉树递归和回溯都需要抽象思维。
- 第81天到第105天:集中攻坚动态规划和图论,同时开始按周做整套的模拟训练,把曾经做过的题拿出来二刷、三刷。
这套节奏比较适合基础一般的人。基础好的话可以直接跳到中后期阶段,但不要跳过“复习”这一步。
2.2 每日时间片的安排:优先级排序
关于每天什么时间刷题,我试验过几种方案:早起刷题、午休刷题、晚上刷题。最终稳定下来的是“早上刷难题,晚上做复盘”。
早上头脑最清醒,适合思考回溯和动态规划这种需要建模的题目。晚上睡前整个人的状态虽然偏疲惫,但适合做不需要太动脑的复习——把之前AC的题重新看一眼,回忆一下核心思路,顺手在笔记里打个卡。午休时间不刷题,最多看一两篇题解,避免影响下午工作和精神状态。
有些人喜欢晚上刷题刷到深夜,我个人不推荐,因为算法题很容易产生“心流”,一上头就两三个小时过去了,直接影响第二天的工作和学习效率。长期来看并不划算。
2.3 刷题记录表:我的三列记录法
这105天里我一直维护一张简单的记录表,只有三列:题目、首次AC用时、复盘状态。不写任何复杂评价。
- 第一列记录题目名称和所属分类。
- 第二列记录第一次完整做出来的耗时,超过30分钟标记为“红”,20分钟以内标记为“绿”,中间标记为“黄”。
- 第三列记录复习状态:未复习、已复习1次、已复习2次。
这个方法看着朴素,但特别好用。它能直观看到哪些知识点是薄弱的。比如我自己的红色标记大量集中在动态规划的状态转移上,后期就专门针对它做了密集训练,肉眼可见地改善了问题。
3. 高频考点的分类拆解与题目选择逻辑
3.1 数组、字符串与双指针:一切编码基本功的底座
这套150题里,数组和字符串是出题数量最多的类型,但不要因为它们基础就轻视。双指针、滑动窗口、前缀和都建立在数组和字符串的灵活操作之上。
以双指针为例,它的核心思想是通过两个指针的移动来减少一层循环遍历。最经典的应用场景之一是“有序数组的两数之和”,用一头一尾两个指针,向中间逼近,能稳定做到O(n)时间复杂度,而暴力解法是O(n^2)的。这个转变在面试中很值得展开讲,面试官考察的正是你能不能跳出暴力方法。
滑动窗口是双指针的延伸版本。核心在于维护窗口内的状态,每次移动右指针扩张窗口,不满足条件时就移动左指针收缩窗口。举一个典型例子:给你一个数组,找出和大于等于某个目标值的最短子数组。固定左端点枚举右端点的写法O(n^2),但滑动窗口可以把问题在线性时间内解掉。这类题目在面试中的出现频率相当高。
实操建议是:先把双指针基础题刷到闭着眼睛能写出来的程度,再进入滑动窗口。很多人在滑动窗口上卡住,本质上是因为双指针移动时机没吃透,而不是窗口这个新概念有多难理解。
3.2 链表:指针操作的“指上功夫”
链表题是面试中性价比最高的类型。它思路往往不难,但写起来容易出错,特别考验对指针引用的理解。链表的反转、合并、删除、找环,每一个都值得反复练习。
链表题有一个天然的优势:刷起来很快,一道题20分钟足够完成从理解到AC。所以它是一个非常适合建立刷题信心的板块。同时,链表的操作技巧也是后续LRU缓存、复杂链表复制等题目所需要的底层能力。
一个非常重要的经验:链表题一定要画图。哪怕是在草稿纸上画三个节点两条指针,也能避免大部分操作顺序问题。我见过很多人在写“反转链表II”时出bug,原因就是没想清楚pre、cur之间的关系。画图十分钟,排查两小时的苦,我替大家吃过了,记住这个教训。
3.3 二叉树与递归:面试的“分水岭”
二叉树这个板块很有意思。说它基础,是因为每个节点都做同样的事,可以用递归很好描述;说它难,是因为一旦碰上路径组合、最近公共祖先、层序遍历变形题,很多人递归返回值就理不清了。
二叉树题目最核心的思维方式是:不要站在全局看问题,把目光聚焦在“当前节点需要做什么”。就像一棵树的递归处理,你只需要定义清楚空节点返回什么、当前节点做什么、子节点递归结果怎么用。至于子树再往下的细节,交给递归本身就好了。
刷二叉树题目时,我强烈建议把三类遍历方式彻底吃透:前序(根左右)、中序(左根右)、后序(左右根)。不要死记模板,要理解遍历顺序背后的信息增量。比如中序遍历二叉搜索树得到有序序列,这就是二叉搜索树相关题目大量使用中序的原因。再比如后序遍历适合用在自底向上返回信息的场景,典型如计算二叉树深度、判断平衡二叉树。
3.4 回溯:在“选择-递归-撤销”里找手感
回溯是很多人第一次刷150题时最容易停滞的地方。它的代码结构其实非常统一,核心就是一个循环里做三件事:做选择、递归进入下一层、撤销选择。
以全排列为例,模板大概长这样:
def backtrack(path): if len(path) == n: res.append(path[:]) return for num in nums: if num in used: continue used.add(num) path.append(num) backtrack(path) path.pop() used.remove(num)这个模板里,path.pop()和used.remove(num)就是“撤销选择”的关键。少了这两行,结果里就会出现重复的排列。很多人初学回溯时觉得难,其实不是难在递归,而是难在“为什么做完选择之后还要撤销”。
生活中可以打个比方:回溯和走迷宫一样。你走到一个岔路口,先选择一条路走进去,如果发现走不通,就得退回到岔路口,再试另一条路。这个“退回到岔路口”的动作,在代码里就是撤销选择。理解了这一层,回溯的基本功就算打下了。
这150题里的回溯题目覆盖组合、排列、子集、棋盘、单词搜索几类题型,它们都是这个模板的变形。把模板吃透之后,剩下的就是剪枝优化的问题了。
3.5 动态规划:从暴力递归到状态转移
动态规划是整个150题里难度最高的部分,没有之一。它考验的不是编码能力,而是建模能力。初学者最常见的问题是把状态定义和状态转移混在一起想,结果越想越乱。
我建议按四步来拆:
- 明确状态。问自己:这个问题的每个子问题可以用几个变量来描述?
- 明确dp数组含义。dp[i]表示什么?或者dp[i][j]表示什么?
- 找转移方程。当前状态可以由哪些更小的状态推导出来?
- 确定初始化和遍历顺序。
以经典的爬楼梯问题为例,问题很简单:每次可以爬1阶或2阶,爬到第n阶有多少种不同的方法。状态定义:dp[i]表示爬到第i阶的方法数。转移方程:dp[i] = dp[i-1] + dp[i-2],因为到达第i阶,要么从第i-1阶走1步,要么从第i-2阶走2步。初始化:dp[0]=1,dp[1]=1。这四步一落地,代码几乎就自动出来了。
3.6 图论、二分、堆、位运算:点缀但不可忽视
150题里图论的占比相对较少,但岛屿数量、课程表、克隆图这几道题基本被反复考。岛屿数量那道题看起来是二维网格,但本质上就是图论中的连通分量个数问题,用DFS染色就能解决。课程表那道题是拓扑排序,可以套用“入度表+BFS”的框架。这些题模型固定,背好模板之后属于送分题。
二分查找的题目看似简单,但边界条件很容易出错。while left <= right还是while left < right,mid = (left + right) // 2还是mid = (left + right + 1) // 2,这些都是经典的区分子问题。建议总结一套自己习惯的边界处理方式,不要每次重新推理。重新推理在面试的紧张氛围下非常容易出错。
堆通常只靠几道题就能掌握:数组中的第K个最大元素、前K个高频元素、数据流中的中位数。这三道题背后对应三种堆的典型用法:大小堆配合、哈希计数+堆排序、动态维护中位数。
4. 我沉淀下来的三张模板卡和一张复杂度速查表
4.1 模板卡一:双指针框架
双指针的解法虽然灵活,但大部分都逃不出这个框架:
left, right = 0, len(nums) - 1 while left < right: if 满足条件: 更新答案 left += 1 # 或者 right -= 1,视情况而定 elif 当前值太大: right -= 1 else: left += 1这个框架适用于有序数组中的查找、区间收缩类问题。核心注意点:移动指针之前先考虑是否要更新答案,移动指针之后不要忘记下一次循环的条件是否仍然满足。
4.2 模板卡二:二叉树递归通用写法
def dfs(node): if not node: return 0 left = dfs(node.left) right = dfs(node.right) # 根据题目要求处理当前节点 return 1 + max(left, right)这套模板几乎可以套到深度、直径、路径和等所有二叉树题目。关键区别在“处理当前节点”那一步:如果问的是深度,返回的是左右子树最大深度加一;如果问的是路径和,可能需要额外维护一个全局变量。
4.3 模板卡三:动态规划五步法
1. 确定状态(dp[i] / dp[i][j] 的含义) 2. 确定转移方程(当前状态与前面状态的关系) 3. 确定初始化(边界条件,比如 dp[0] 或 dp[0][0]) 4. 确定遍历顺序(正序还是逆序、一维还是二维) 5. 手动推演两三组样例再写代码第五步是我自己加的,但非常管用。不要一上来就写代码,先在草稿纸上手动推导几个小例子,确认转移方程能算对,再开始编码。这能省下一大半调试时间。
4.4 复杂度速查表:写代码前心里有数
| 数据结构/算法 | 时间复杂度 | 典型场景 |
|---|---|---|
| 数组随机访问 | O(1) | 下标查值 |
| 有序数组二分查找 | O(log n) | 搜索旋转排序数组 |
| 链表头部插入/删除 | O(1) | LRU缓存 |
| 二叉树遍历 | O(n) | 中序、前序、后序 |
| 哈希表平均 | O(1) | 两数之和、频率统计 |
| 堆的插入/删除 | O(log n) | 第K大、前K高频 |
| 图的DFS/BFS | O(V+E) | 岛屿数量、课程表 |
| 动态规划 | O(n*m) 常见 | 编辑距离、路径问题 |
为什么强调复杂度一定要记?因为面试官几乎在每道题写完后都会追问一句“时间复杂度是多少,空间复杂度呢”。如果答不出来,即使代码跑通了也是严重减分项。
5. 刷到100天才明白的几个天坑与解法
5.1 天坑一:看题解的时候秒懂,合上题解秒忘
这是几乎每个人都会遇到的情况。打开题解,逻辑清清楚楚,标题写着“一看就会”,自己动手一写就废。原因很简单:看题解是被动接收信息,自己写是主动输出信息,中间差距巨大。
解决办法是“合上书复盘法”。看完题解之后,先合上任何参考,自己在空白编辑器里重新实现一遍。如果中途卡住了,允许回头看题解,但看完以后必须再从头写一遍,直到能完全凭记忆和理解写出通过全部测试用例的代码为止。这个步骤花费的时间可能是直接抄代码的三倍,但效果也是直接抄代码的三倍。
5.2 天坑二:只刷新题不复习,存量清零
头一个月的我就是这样,每天都追求新题,觉得复习旧题是浪费时间。结果到了第30天,回头看第5天做过的题,竟然一点印象都没有,仿佛从没做过。这给我狠狠上了一课。
后来我强制自己执行“二刷”节奏:每往前推进10道新题,就必须回头复习3道旧题。这个比例并不高,但已经足以保持记忆的连续性。到了后期,我甚至发现二刷的收益远大于刷新题,因为解题速度明显提升,思路也清晰了,这给了自己很大的正反馈。
5.3 天坑三:死磕难题,一卡就是一晚上
刷题时有一种心态很危险:“今天不把这题AC了不睡觉”。我自己在动态规划上栽过好几次。一道题想了一个半小时之后,大脑已经进入了疲劳状态,继续硬想不但没有效果,反而会积累挫败感。
我现在遇到超过30分钟还没有任何思路的题,会直接看题解,然后在笔记里标注“难题标记”,等两三天后回头重做。这不算偷懒,这叫做“带着答案去理解,再独立去输出”。学习算法不是比谁硬想的时间长,而是比谁掌握的工具多、理解得深。
5.4 天坑四:轻视“讲题”这个环节
算法面试不只是写代码,更要会用语言把自己的思考过程表达出来。很多刷题顺利的人会忽略这一点,总觉得自己能写出来就够了。实际上,面试官更看重你如何拆解问题、如何提出不同解法、如何分析复杂度。
从第80天开始,我给自己加了一个训练:每做完一道题,就用3分钟口头复述一遍解题思路。如果发现自己说得颠三倒四,说明对题目还没有真正吃透。这个习惯帮助我在面试模拟时表现得比之前自然很多,也让我更加理解为什么很多刷题量很大的人会在面试中发挥失常。
6. 第105天之后:从刷题到面试状态的切换
现在的时间节点是3月6号,已经进入春季招聘的密集准备期。对大部分人来说,150题完整刷完并不是终点,而是从“输入”转向“输出”的拐点。
接下来我给自己安排了三件和刷题关系不大的事:按周做整套模拟训练,限定45分钟完成一道中等难度题并用10分钟做口头讲解;整理自己的错题集,把重复犯的错误分类归纳;花两三天时间专门梳理之前忽略的边界条件类问题,比如空输入、单个元素、负数、溢出、重复值等。
这套150题的题单本身足够经典,我不建议再做大量扩充。把它刷熟、讲透,比刷更多新题更有价值。等到很多面试题在你眼里都能自动映射到某一类经典模型时,刷题的意义就已经完成了,剩下的交给临场心态和沟通表达。这也是我在第105天最大的体会——刷题刷到最后,练的不只是代码,更是底气和讲故事的逻辑。