
今天是3月6日。打开刷题日历第105天。这一百多天里我几乎没有间断地和“leetcode面试经典150”打交道。今天这篇不是鸡汤打卡也不是晒进度而是想认真聊聊这套150题到底应该怎么刷、刷到什么程度算够、以及它和我真实面试中的体验差多少。如果你正在准备算法面试或者已经刷了一段时间但总觉得自己在原地打转那这篇文章正好是写给此刻的你的。1. 为什么我坚持把“面试经典150”当作主线题库1.1 150题的结构刚好卡在“质”和“量”的平衡点上先聊一个很多人纠结的问题为什么不是按题库全量刷或者直接刷高频题合集我自己最开始也走过题库全量刷的路结果一周不到就放弃了——每天打开界面都要纠结今天做什么做完一道题心里也没底不知道在全景图里处于哪个位置。后来切到“面试经典150”最大的改变是我有了一个边界清晰、可以被完整走完的路线图。150题是一个很聪明的数量级。按主题来算平均每个核心主题能分到10题左右按时间来算每天2到3题两个月出头就能完整走完一轮。这个节奏对我来说刚好——既不会因为题太多而丧失掌控感也不会因为每天只能练两三道而从“练题”变成“背题”。更重要的是它把“广度优先”和“重点覆盖”做了平衡。全站几千题里真正高频出现的考点在这150题里基本都有对应位置而那些偏门到面试里几乎不会出现的题目它也不会硬塞给你。我见过有人质疑“150题不够”理由是面试官可能会问到没刷过的原题。这种担心有一定道理但解决方式不是去追求把所有题都刷完而是把核心题型的“可迁移模型”吃透。“面试经典150”给你的不是一个答案库而是一组可以组合的算法组件。比如你刷透了“二叉树的前中后序迭代遍历”遇到各种树的题目你都能清楚知道该在哪里做手脚。1.2 这套题和算法面试的真实需求对比真实的技术面试尤其第一轮通常围绕数组、字符串、链表、二叉树、二分、回溯、DFS/BFS、动态规划基础来出题。打开“面试经典150”的题型目录你会发现它几乎就是按这个清单来编排的。一个很直观的例子滑动窗口、双指针、区间合并、哈希表这几类题目在面试里出现频率极高而这套题在早期就用大量题目把这几类讲透了。有人觉得题目太简单刷一遍很快。但我的理解是面试考察的不只是“会不会解”还包括“能不能讲清楚为什么这么解”。150题里的“简单题”恰好适合用来训练表达能力为什么这个循环要这么写为什么这道题可以用双指针而不用哈希表这些话说顺了面试时才不会只丢给面试官一段干巴巴的代码。所以我的总原则是把“面试经典150”当成主餐别急着去搞偏题怪题。它的上限不是150题本身而是你能把每一道题扩展到多少个同类问题。2. 3月6日的实战复盘从两道滑动窗口到一次区间合并2.1 当天做题顺序与时间分配3月6日那天晚上我给自己安排了一个半小时。已经进入二刷阶段所以我不追求“做新题”而是把重点放在最近暴露短板的两个主题上滑动窗口和区间。安排是这样的先做“最小覆盖子串”然后做“无重复字符的最长子串”最后做一道“合并区间”。做题顺序也有讲究——先做最不熟的因为在精力最足的时候处理难点把相对简单的放在中间当调剂最后用一道“确定性较高”的题收尾给自己留个正反馈。如果算上休息时间三道题用时大概是最小覆盖子串45分钟无重复字符最长子串15分钟合并区间20分钟。剩下的时间我全部给了复盘后面会专门说复盘怎么做的。2.2 最小覆盖子串我今天卡得最久的一道题“最小覆盖子串”是滑动窗口里很有代表性的难题也是面试常客。题面很简单在s中找最短的子串让它包含t中的所有字符。但我今天还是卡了。卡点不在“知不知道用滑动窗口”而在“valid变量的增加和减少时机”。如果你也写过这道题大概率经历过这样的时刻right移动时窗口里多了字符left移动时窗口里少了字符但什么时候valid加一、什么时候减一多写几遍就容易出现偏差。我的解法把状态分成两个表need表记录t中每个字符需要的数量window表记录当前窗口里每个字符的数量。只有当window中某个字符的数量和need中的目标值相等时valid才加一。当窗口满足条件后开始移动左指针缩小窗口如果左指针移出的字符会让对应状态不再满足条件valid就减一。这个模式我后来提炼成一个通用框架右指针负责“使窗口满足条件”左指针负责“在满足条件的前提下让窗口尽量小”。写代码时把两件事分开逻辑就会清晰很多。今天犯的错误就是一开始把左指针移动前先判断窗口是否有效写错了位置导致跑到一半valid清零直接浪费了二十分钟。public String minWindow(String s, String t) { MapCharacter, Integer need new HashMap(); MapCharacter, Integer window new HashMap(); for (char c : t.toCharArray()) { need.put(c, need.getOrDefault(c, 0) 1); } int left 0, right 0; int valid 0; int start 0, len Integer.MAX_VALUE; while (right s.length()) { char c s.charAt(right); right; if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) 1); if (window.get(c).equals(need.get(c))) { valid; } } while (valid need.size()) { if (right - left len) { len right - left; start left; } char d s.charAt(left); left; if (need.containsKey(d)) { if (window.get(d).equals(need.get(d))) { valid--; } window.put(d, window.get(d) - 1); } } } return len Integer.MAX_VALUE ? : s.substring(start, start len); }这类题卡住的根本原因是不能凭空想象窗口的状态必须自己动手模拟一遍“当right移动后窗口内某种字符数量与need保持一致时valid加一”的过程。把极端例子画出来比如t中包含重复字符的情况就能避免很多边界错误。2.3 合并区间与“边界不变量”“合并区间”的核心是排序加合并。先把区间按起点排序然后遍历用当前区间去和结果列表的最后一个区间比较如果重叠就更新右端点如果不重叠就加入结果列表。这个思路看起来简单但很多人写出来会有小毛病比如不小心改了正在遍历的区间或者重叠判断写成“后一个区间起点小于等于前一个区间终点”时忘了相等也要合并。这里我想强调一个概念刷题时一定要锁定“不变量”。在合并区间里不变量就是“结果列表中最后一个区间已经合并到当前最优状态”。之后每读一个新区间只需要和这个状态比较不需要回看更早的区间。这个思维方式通用性很强很多贪心题都是靠“明确不变量”来避免越写越乱的。写这类题的稳定套路是结果列表单独维护始终拿结果列表中最后一个区间和当前区间比而不是去修改正在遍历的区间。2.4 复盘后我给自己留的一个问题当天刷完我并不着急开始第二天的题。我在笔记里留下了几个问题滑动窗口里valid的维护顺序能不能用模板统一区间重叠判断能不能直接用“previous.end current.start”作为条件DFS染色题里防止重复访问是用visited数组还是原地改字符更稳。这些问题不一定马上有答案但带着问题去刷后面的题效率比盲目刷高不少。我还会把当天最值得记的一句话写下来。3月6日那天写的是“窗口类问题右指针负责满足条件左指针负责最优答案。”这句话后来在我做“无重复字符的最长子串”和“串联所有单词的子串”时都起到了快速定向的作用。3. 刷题105天我被问得最多的三个方法问题3.1 错题本到底记什么我说的不是收藏题号、粘贴最佳题解那种笔记。那种笔记刷一次就知道回看率极低价值有限。我的错题本里只放三类内容第一类是“卡住现场”。比如今天在最小覆盖子串上卡了二十分钟我会写“left移动后不确定valid减一的时机。”第二类是“根因判断”。我会尝试回答为什么当时会卡是因为没识别出题型还是某个边界条件没想清楚还是对某个API不熟第三类是“下一次的检查点”。比如“下次看到子串覆盖最短先想滑动窗口模板写完代码后用s‘aa’t‘a’这类最小样例自测。”这三类内容都有共同点它们不是为了“收藏”而是为了下一次见到同类题时有可用的触发线索。学习算法和学习语言很像重复接触加上主动回忆比单纯抄写有用得多。我见过不少同学错题本里贴满了代码几乎原样从题解里摘下来的但真到面试前翻看时根本想不起当时为什么要这样写。不如用大白话把“思维过程”记下来那才是你自己的东西。3.2 二刷计划怎么排才不会变成“重刷一遍”一刷的时候我是按官方主题顺序刷的好处是能集中建立每个主题的局部框架。但二刷如果还按同样顺序很容易变成“看到题目就想起答案”然后草草地AC一笔带过。这样的二刷意义不大。我自己的做法是打乱主题混刷。比如今天刷一道滑动窗口、一道二叉树、一道动态规划明天又是一道图论、一道双指针。混刷的核心目的是训练“题型识别”因为在真实面试里你不会提前知道下一题考什么。当你面前只有题目没有分类标签时那种“这个题很像滑动窗口但边界条件有点怪可能要用前缀和辅助”的判断力才是二刷真正要练的东西。另外二刷时我会刻意避开“曾经AC且非常熟练”的题目。这是一个反常识的选择如果不算熟练说明还有增量空间如果太熟练刷它只是在重复舒适区不如把时间花在真正的薄弱点或者用旧题的新变体来提高阈值。判断标准很简单拿到题十秒内如果能直接说出完整解法这道题就暂时不用再花时间了。3.3 先讲思路再写代码这个习惯帮我省了至少一半复盘时间很多人刷题时有个坏习惯读完题立刻扑到编辑器上边想边写最后AC了复盘时发现自己也说不清中间几个关键决策是怎么做的。我大概是第三十几天开始强制自己换流程先把题目读懂然后在笔记里口述一遍大致思路——用什么数据结构、时间复杂度多少、边界条件怎么处理最后才写代码。一开始这个流程很别扭尤其是简单题会觉得“这不直接写就完事了吗”。但坚持一段时间后好处非常明显。首先口述思路会暴露“自以为懂了却没懂”的地方其次代码写错的时候你可以回看自己的思路记录快速定位认知偏差而不是对着报错发呆最后这是性价比最高的模拟面试方式——你不需要真的面对面试官就能练到“用语言清晰地解释算法”的能力。如果你现在刚起步我建议可以从“每道题写三行解题思路再动手”开始。不用很长说清楚用什么方法、分几步就行。等这个习惯稳定了再逐步加入复杂度分析、边界检查、用例设计最后变成完整的口述面试流程。4. 刷完150题离面试通过还差哪几步4.1 高频交叉点数组、字符串、二叉树、图论刷完“面试经典150”之后我最大的感受是很多题目看似不同底层却是同一套东西在反复组合。数组题练的是下标和边界字符串题练的是字符处理和哈希表二叉树练的是递归和遍历顺序的敏感性图论题练的是DFS/BFS的状态标记。我总结了一个很粗暴的分类方式适合用来快速归类面试题看到“连续子序列、区间、重叠、最短覆盖”就往滑窗/双指针/前缀和上想看到“网格、岛屿、连通分量、被围绕区域”就往DFS/BFS上想看到“选择、不选、求方案数、最值”就往动态规划或回溯上想。这些判断不是绝对正确但能帮你快速找到第一大方向。4.2 面试现场和刷题社区的区别刷题社区的体验和真实面试有很大区别。社区里你面对的是题目本身提交后要么AC要么WA机器会告诉你结果面试现场面对的是人代码写对了不一定满分思路能不能讲清楚、边界要不要主动讨论、卡住之后会不会沟通这些都会影响评价。我有一次印象很深的面试题目本身不难很简单的一道双指针但因为太紧张直接把right指针和left指针的意义讲反了。面试官提醒了一下我立刻纠正过来代码也写对了。后来复盘时我想如果平时刷题就习惯“先讲思路再写代码”这种紧张时刻其实是可以靠大量重复训练来缓解的。所以刷题不只是刷代码还要有意识练“讲解”。还有一点容易被忽略面试时主动解释复杂度的能力。很多题你会用某个数据结构但如果面试官追问“为什么这个操作是O(1)”或者“有没有空间复杂度更低的方案”平时没有练过当场很容易卡壳。我建议每次AC之后顺手在笔记里写句“这道题时间O(n)空间O(k)其中k是窗口/哈希表大小”。这个小动作会让复杂度分析变成一种条件反射。4.3 把题目抽象成范式比记住题号更重要记住题号没有意义因为面试不会照着题号问。但记住“范式”有用。举个例子滑动窗口的本质是维护一个区间满足某种性质时更新答案违背性质时收缩边界。这个范式一旦内化你在看到“子串”“子数组”“覆盖”“最长”“最短”这些词时会自然进入对应的思考通道。第二个高频范式是“排序让贪心成立”。区间合并、会议室安排、最少箭矢射气球都是这个范式的变体。第三个是“图论问题先确定遍历方式再确定状态标记”很多DFS/BFS题都是在递归里多带一个状态或在visited数组中多存一层信息。我建议刷完一遍后把“面试经典150”按范式重新梳理一遍比如把所有滑动窗口题放一起、所有区间题放一起、所有DFS/BFS题放一起做一次主题总结。这一步会帮你从“我会做某道题”提升到“我能顺手解决一类题”。5. 打卡105天的节奏管理日历、记录与低谷期5.1 我的轻量打卡方式不追进度只追连续很多人看到“day105”会以为我每天一定刷了很多题。真实情况不是这样。我给自己定的底线非常低哪怕只做一道题哪怕只是读了一道难题再想清楚思路只要在日历上打了一个勾这天就算数。打卡的意义不在单日工作量而在于让大脑一直保持对算法的敏感度。中断一次再想恢复往往需要三四天甚至一周才能回到原来的状态这中间的时间成本远大于“今天少刷点但坚持住”的代价。这个经验来自我早先的一次断档因连续出差断了三天回来后做题手感明显生涩很多本来熟悉的题都要重新想很久。从那以后我彻底放弃了“单日两小时”的执念改成“每天至少接触一道题”。5.2 状态不好时我允许自己“只读题不写码”状态不好的时候比如加班到很晚回家又或者脑子昏沉沉的我有一套保留操作只读题不写码。也就是把当天计划里的题目读一遍在纸上画一画样例简单写一下思路看看题解然后合上电脑睡觉。第二天我会重新做一道同类型题检验前一天留下的思路是否正确。这个方法看起来偷懒实际上能帮很大忙。因为很多难题的解法不是直接从零想出来的而是“先看到、再理解、最后内化”。你允许自己在低功耗状态下接触新题第二天换一个清醒的脑子去做同类题往往会有种豁然开朗的感觉。这比硬撑着写一堆跑不通的代码要有效得多。有一点要提醒这套“只读题不写码”只能用在低功耗日不能天天这样。如果你连续一周都只读题不写码那就不是维持手感而是在假装努力。我会用日历上的标记来监控每七天里最多允许一天是“轻量模式”其他六天必须至少写完一道题的完整代码。5.3 关于“刷完一遍”的重新理解“把150题刷完一遍”听起来是个线性目标但真正做完之后你会发现一遍只是一个起点。二刷三刷的重点不是那些一眼就会的题而是“会但不熟”“懂但讲不清”的题。我给自己定的三刷标准很简单拿到题十秒内能说出题型两分钟内能讲清算法框架十五分钟内能写出基本正确的代码并覆盖常见边界。如果某道题达不到这个标准它就会被移到下一轮继续刷。判断刷题是否有效不是看AC数有多高而是看遇到新题时你能多快联想到已经内化的算法范式。这个过程没有终点但“面试经典150”这个清单帮我建立了一个足够稳定的起点。如果有朋友在纠结“刷二遍是不是浪费时间”我的答案是当你对着题还能讲出“这和某个旧题是同一个模型”时这一遍就没白刷。说起来3月6日那天我最后关掉电脑前在日历上画了一个新勾。一百多天前这个清单前一页还是一个对算法又好奇又没底的普通打工人一百多天后的今天我仍然不觉得自己是算法高手但至少面对大多数面试级题目时心里有了一条清晰的路径。如果你也在刷它不用急把每天那个勾保留住剩下的交给时间。