65天刷完118道经典150题,我总结的算法面试刷题方法
1月24日打卡第65天。晚上九点合上电脑屏幕右上角显示今日完成2道题累计118/150。这套“面试经典150题”刷到现在总算过了三分之二。看到这个进度很多准备开始刷题的朋友问题库里几千道题为什么偏偏选这150道65天才到118题是不是太慢了先说结论不是。经典150题是刷题平台官方维护的高频面试题单覆盖数组、字符串、双指针、滑动窗口、哈希、链表、二叉树、回溯、动态规划、贪心、图论、堆等几乎所有面试考点。每个知识点只挑最典型、最常考的几道不追求题海侧重“做过一道就能迁移到一类题”。我记得很清楚第20天的时候还在双指针里绕圈第45天啃动态规划时常态崩溃但坚持到第65天看到新题第一反应不再是慌张而是条件反射地判断这题考什么结构、有没有重叠子问题、能不能用两个指针划一下。这种变化不是靠刷题数量堆出来的是靠“题单的覆盖度每天规律的复盘”一点点磨出来的。这篇文章不聊鸡汤只聊我刷完118题后沉淀下来的选卷理由、题型拆解、刷题节奏、踩坑实录和经验总结希望给准备开始或正在刷这150题的朋友一些可落地的参考。1. 为什么选经典150题题单价值与节奏规划1.1 这150题的筛选逻辑刷它到底图什么刷题平台上的题目总数很多但对面试做准备的人来说全量刷完既不现实也没必要。经典150题的价值在于“官方已经帮你做了一遍筛选”它把数据结构与算法里最高频的考点压缩成一份清单让你用相对可控的时间覆盖绝大多数面试场景。具体覆盖范围大致包括数组与哈希表、双指针与滑动窗口、字符串处理、栈与单调栈、链表、二叉树、完全二叉树的遍历、回溯与DFS/BFS、动态规划一维、二维、区间、背包、贪心、图论拓扑排序、并查集基础、堆、位运算、数学技巧等。比如“两数之和”考哈希“三数之和”考排序加双指针“无重复字符的最长子串”考滑动窗口“爬楼梯”考一维DP“全排列”考回溯模板这些题型在150题内都至少出现2到3道代表题目。很多人的误区是“刷一百题但题型高度重复”导致遇到新题依然不会。经典150题的设计恰恰是压低重复度、提高覆盖面每一道题几乎都有独立的知识点或技巧点做完一遍相当于把所有核心思想快速过了一遍。我在第1天到第10天只刷数组和哈希相关题目到第11天遇到链表反转时明显感觉“基础数据结构不熟”会成为瓶颈。所以如果你也想采用这套题单我的建议是不要跳过前期的简单专题它们很重要是后面理解复杂结构的基础。1.2 65天刷到118题时间到底怎么排65天完成118题平均每天不到2题这个节奏看似不快但实际执行下来非常合适。原因很简单刷题表面拼的是数量实际比的是理解深度和迁移能力。一天憋出5道题但第二天全忘不如一天认真拆解2道题把边界条件、复杂度推导、优化路径都搞明白。我按三个阶段来划分大家可以直接参考也可以根据基础调整。第一阶段大约20天数组、哈希、双指针、滑动窗口。这部分以medium偏easy为主每天可以安排2道新题主要是找回手感。我在这个阶段给自己定的规矩是“每道题必须能说出暴力解和最优解的复杂度差异”比如“三数之和”如果只知道排序加双指针却说不出为什么排序能去掉重复三元组那就说明还没吃透。第二阶段大约25天链表、二叉树、回溯、DFS/BFS。这部分难点在递归思想和指针操作每天1到2道新题再加上1道旧题重刷。链表题最容易出现指针指向迷路二叉树题最容易漏掉递归终止条件这一阶段必须放慢把草稿纸画图和递归栈推导用起来。第三阶段大约20天动态规划、贪心、图论、堆。这部分是公认的硬骨头我特意安排每天1道新题“保底”状态好再补第2道。动态规划题上手慢开始时连“状态转移方程”都写不顺所以最重要的是先积累模型比如背包、编辑距离、最长递增子序列对于同类题要能快速归约。另外无论哪个阶段我每周都固定留半天做“错题重刷”只刷标记为“易错”“没思路”的题目。这一步看着不起眼但真正解决“刷了就忘”问题的是它不是新题数量。2. 高频题型拆解拿到新题先套哪套框架2.1 双指针与滑动窗口很多最优解就藏在两个指针里双指针是经典150题里出现频率很高的技巧常见场景是数组、字符串子串类问题。核心思想很简单一个指针做基准另一个指针根据条件移动把两层甚至三层循环降成一层循环。以“三数之和”为例暴力做法是三重循环时间复杂度O(n^3)去重还会很痛苦。排序加双指针的做法是先对数组排序固定一个数再用左右指针找另外两个数时间复杂度降为O(n^2)。这里的额外收益是排序后相同的数字会相邻去重就简单了——跳过和上一个相同的元素即可。很多人面试时能写出双指针代码但被问到“为什么排序能解决去重”就卡住核心原因是对“相同元素的位置关系”理解不够深。滑动窗口本质也是双指针的变种。“无重复字符的最长子串”就是经典例子右指针负责扩展窗口左指针负责在出现重复字符时收缩窗口。难点不是写代码而是搞清楚左右指针何时移动、哈希表记录的是字符还是下标、窗口长度怎么计算。我第一次刷这道题时把窗口长度算错了原因是右指针减左指针后忘了加1这种边界细节只有在复现代码和跑测试用例时才能发现。我自己的经验是遇到“子串”“子数组”“连续区间”这类关键词优先往双指针、滑动窗口方向想遇到“有序数组”就优先想二分和双指针。刷到第50道左右这种条件反射基本就建立了。2.2 动态规划先写暴力递归再找状态转移动态规划大概是150题里劝退率最高的板块。我的入门方法是四步走。第一步写递归版本的暴力解即使超时也先写出来第二步在递归过程中找重叠子问题第三步把递归改写为DP数组填表第四步看能不能数组降维。以“爬楼梯”为例递归思路是f(n)f(n-1)f(n-2)暴力递归会重复计算大量子问题。改成DP后用一个一维数组从f(1)、f(2)开始递推时间复杂度稳定O(n)。再进一步因为每次只需要前两个状态可以用两个变量滚动更新这就是空间优化。另一个观察是动态规划的瓶颈往往不在“写转移方程”而在“判断这题能不能用DP”。我个人总结出的信号包括题目问“最多/最少/多少种方案”数据范围又比较大比如n在几百到几万暴力搜索会爆炸这时大概率是动态规划。比如“最长递增子序列”这类题要求“最长”且n可以到几千暴力组合肯定不行就要想到DP或者贪心加二分的变体。2.3 二叉树与回溯递归框架一定要滚瓜烂熟二叉树题的核心是三种遍历框架前序、中序、后序。做题时不要求背代码但脑子里要时刻清楚“当前节点该干什么、什么时候递归左边、什么时候递归右边”。很多题看起来复杂实质是遍历顺序变化求深度就后序验证二叉搜索树就中序重建二叉树就前序加中序配合。回溯问题也有一个固定模板选择、递归、撤销选择。以“全排列”为例递归函数里先判断路径长度是否等于目标等于就把当前路径加入结果否则遍历可选元素如果这个元素不在路径里就选它递归完成后撤销这个选择。模板几乎可以套用到“子集”“组合总和”“括号生成”等一堆题目。我的踩坑点是一开始总忘记“撤销选择”结果路径越走越长输出一堆错误结果。后来我把回溯理解成“走迷宫时在岔路口做个标记回来时把标记擦掉”记忆负担一下降低不少。如果你也经常死循环或者结果重复第一反应检查“撤销选择”是不是漏了。2.4 图论与拓扑排序从依赖关系入手更好懂图论在经典150题里占比不算特别高但拓扑排序几乎是必考点典型场景是“课程安排”这类有依赖关系的题目。解法分三步建邻接表、统计入度、用队列做BFS。为什么每次都要取出入度为0的节点因为入度为0说明这个节点没有任何前置依赖可以优先处理处理完它把它的所有后继节点入度减1减到0的后继又可以入队。整个过程很像解毛线团每次从没被缠住的线头开始抽。如果最后处理的节点数少于总节点数说明图里有环依赖关系不可能被满足。这一个判断逻辑能解决很多看起来不太像图的题。3. 实操过程一道题从暴力到最优的完整心路3.1 以“三数之和”为例完整拆解一次写这类题时我坚持“先把最蠢的办法写出来再想优化”不是浪费时间而是确保对暴力逻辑彻底理解。三数之和的暴力解是三重循环枚举所有三元组然后用set去重。不说复杂度光是去重逻辑就足够让人头大。进入优化时第一步是排序。排序的最直接好处是去重变得可处理因为相同数字会排到一起跳过连续相同元素即可。第二步是固定第一个数i然后用left和right两个指针在i之后搜索两数之和。排序后数组天然有序如果三数和小于0说明left位置的数太小left向右移动如果大于0说明right位置的数太大right向左移动等于0则记录答案同时跳过重复元素。def threeSum(nums): nums.sort() res [] n len(nums) for i in range(n - 2): if i 0 and nums[i] nums[i - 1]: continue left, right i 1, n - 1 while left right: total nums[i] nums[left] nums[right] if total 0: left 1 elif total 0: right - 1 else: res.append([nums[i], nums[left], nums[right]]) while left right and nums[left] nums[left 1]: left 1 while left right and nums[right] nums[right - 1]: right - 1 left 1 right - 1 return res代码本身不复杂复杂的是扣边界。固定i那里为什么要跳过重复因为i作为第一个数如果这个值已经出现过产生的结果集必然重复。left和right跳过重复同理。为什么内层while是left right而不是left right因为两个指针不能指向同一个数否则三元组不够三个数。这些细节光看题解是记不牢的要自己在本地多跑几个测试用例比如输入里全是相同数字、包含负数、包含0才能真正理解。从这道题学到的可迁移经验是看到“和为某个值的组合数量”这类题排序加双指针几乎是通用解法。“两数之和II”和“四数之和”都能用同一条思路变形解决。3.2 刷题笔记这样记一周后还能看懂我记录刷题笔记不是抄题解而是用一种自己看得懂的方式记录四个要点题号、题型、思路、错因。每周末汇总一张表只留最重要的信息。为了方便复盘我给自己设计了一个“二刷状态”标记一刷是独立写出最优解二刷是三天后能不看任何提示复现三刷是两周后能秒出思路并写对。下面是我真实在用的一种表格结构只展示两行示例日期题目编号化题型我的思路一句话复杂度错因或卡点二刷状态1月20日题#42双指针/单调栈左右各站一个指针向中间最高处收缩并累计雨水量O(n)没想到用左右最大值一刷通过3天后再刷1月22日题#72二维DPdp[i][j]表示s前i个字符到t前j个字符的最少编辑次数O(mn)转移方程漏了替换操作需要重点重看表里的“思路一句话”不是正式解题过程而是记忆锚点。过了几天看到“左右各站一个指针”就能瞬间想起整道题的解法。比写长篇大论题解有用得多。建议不要把刷题笔记写成“完整题解”那样等于抄一遍题目过程很舒服但记忆很浅。用关键词和图示逼自己把思路压缩成一句话效果翻倍。4. 常见问题与排查技巧实录4.1 刷完就忘怎么办“刷完就忘”是高频问题我也经历过。第14天重看第3天做过的题连题意都要读半天那感觉确实绝望。后来总结出三条可落地的解决办法。第一条引入间隔重复。当天做的题第1天、第3天、第7天、第14天各快速重看一遍不要重写整个代码只是先回忆思路再对照笔记。第二条把同类题型放在一起对比。比如连续刷几道“滑动窗口求最大值”和“滑动窗口求无重复子串”对比它们的窗口扩张、收缩条件比单独刷十道不同题型更容易形成长期记忆。第三条保证“输出式学习”。每次看完题解合上任何参考自己重新在白纸上推导思路并写一遍代码写不出来就说明没吃透标记为“二刷必做”。我坚信一个道理刷题的目的不是“在题单里画勾”而是让“看到题目就能识别模式”遗忘不可避免记忆机制本身就需要反复提取。4.2 超时和内存超限怎么排查在平台上刷题最常见的两种报错是“超时”和“内存超限”。超时的根源几乎都是复杂度太高。比如n的范围是10^5你的代码写成三重循环哪怕写法再漂亮也大概率超时。排查顺序是先估算算法复杂度和数据范围是否匹配如果不匹配先优化思路而不是调细节如果复杂度没问题再检查有没有死循环比如双指针忘了移动左指针或者while条件被错误地改成永远成立。内存超限则多为数据结构不合理比如题目只要求一个数值你却开了二维数组记录所有中间结果或者递归层数太深导致调用栈过大。遇到这种问题先去审视状态设计能否压缩再看能不能用滚动数组或迭代替代递归。分享一个真实调试案例某一次写“无重复字符的最长子串”我一直超时检查半天发现是左指针移动后哈希表里的旧下标没有及时清理导致条件判断出错窗口疯狂扩张。加了对旧记录的删除后时间从超时降到几十毫秒。这类问题往往不靠灵光一闪而是靠小数据手动模拟一遍或者打日志观察指针变化。4.3 心态崩了一天只做出半道题第45天我刷一道图论题看了四十分钟题解都绕不过弯最后放弃那天记录是“0.5题”。后来我想明白了这件事刷题不是必须每天“满血通关”状态起伏很正常与其卡死在一道难题上不如定死一个底线——无论状态多差当天至少也把题目的暴力解想明白或者把题解的核心步骤抄写并推导一遍。如果发现自己连续三天没思路我建议暂停新题回头做已经会的题。不是因为“逃避”而是因为“恢复手感”更有价值。睡眠充足的时候做动态规划题和熬夜硬刚时做动态规划题完全是两种体验。保护好精力和心态刷题才能长期主义。我给自己设了一条红线晚上十一点之后不碰新题最多看看旧的错题笔记。这条红线让第二天状态好了不少。4.4 常见问题速查表问题可能原因优先排查方向本地运行对提交超时复杂度太高或存在死循环先估算复杂度是否符合数据范围再检查循环是否每轮都有终止变化结果重复回溯忘记撤销选择或未跳过相同元素检查递归里的撤销步骤检查固定位置的重复跳过逻辑内存超出状态数组过大或递归过深压缩状态、用滚动数组、把递归改成迭代边界输入报错没有考虑空输入、单元素、全相同元素每次提交前手动跑最小输入和极端输入看了题解写对了隔天又不会记忆提取不足用间隔重复隔天先回忆思路再写代码5. 从150题到面试实战最后一步怎么走5.1 150题刷完之后往哪延伸刷完150题不等于万事大吉更关键的是后续方向怎么选。我准备按三条线延伸。第一条线是专题强化。发现动态规划弱就专门找20道高频DP题突破发现树相关总是出小错就集中把二叉树遍历、路径和、最近公共祖先这类题过一遍。第二条线是模拟面试。每周抽两个晚上给自己限时45分钟用白板写题全程不查资料锻炼“边写边讲”的能力这和平时刷题完全不一样。第三条线是系统整理套路卡。比如“遇到有序数组优先二分”“遇到Top K优先堆”“遇到字符串子串优先滑动窗口”把常用套路做成自己的私房清单面试前翻一遍比临时刷题更有效。5.2 面试时怎么讲清楚一道题很多开发者能写对代码但面试时讲不清楚。我总结的质量性建议是先讲暴力解再讲优化。先问清楚数据范围比如n最大能到多少然后列暴力方案并分析复杂度再说明为什么暴力不够好接着给出优化思路。这样做的好处是即使你优化方案没完全写出来面试官也能看到你的分析能力。例如“两数之和”这类题先讲双重循环O(n^2)的做法再讲“用哈希表把查找降为O(1)”从整体上讲清楚为什么要用空间换时间。面试现场最忌讳的是上来就默写最优解写完了却说不出所以然。面试官真正想考察的是你面对未知问题时如何拆解而不是你是否背下了某个答案。还有一个经验如果完全没思路宁可直接说“我先想一个暴力解”也不要沉默。把暴力解说出来至少展示你能分析问题然后再尝试用见过的模式去靠近优化解。面试官也是人能看到思考和挣扎的过程。刷了118道题之后回头看这套经典150题它最大的价值不是让我记住每一道题的答案而是让我建立了“看到问题先归类、再套框架、最后优化细节”的解题习惯。第65天不是终点距离完成150题还有32道。接下来我的计划很简单把剩下的题按专题啃完错题重新过一遍然后进入模拟面试环节。如果你也在刷这套题单给自己一个合理的节奏哪怕每天只做一道只要坚持做总结和复盘到第100天回头看一定会看到明显变化。最后分享一个小技巧每天睡前用10分钟在脑子里快速回放当天做过的题目的核心步骤如果能顺利回忆出“用了什么思路、时间复杂度是多少”就算真正吸收了。这个习惯我坚持下来后遗忘速度明显变慢算是性价比最高的一笔时间投资。