ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LeetCode Hot 100刷题方法论万字总结:专题分类、套路模板与周赛复盘

LeetCode Hot 100刷题方法论万字总结:专题分类、套路模板与周赛复盘 刷 LeetCode 这件事我前后断断续续坚持了大半年中间鸽了两次、推倒重来三次直到最近才把 Hot 100 完整啃完第一轮又紧接着把错题和薄弱专题重刷了第二轮。这篇“万字总结”不是一时兴起的爽文而是我在反复刷题、写题解、复盘周赛过程中沉淀下来的方法论和踩坑记录。它适合正在准备算法面试的人也适合刚入门、面对 100 道题不知道从哪下手的同学——我尽量把“怎么拆题、怎么写题解、怎么刷才不白刷”讲透而不是单纯列一遍题目清单。先说结论LeetCode Hot 100 并不是 100 道孤立的算法题它更像一整套算法面试的“高频考点抽样”。你可以把它当成地图但真正值钱的不是把每道题的答案背下来而是知道每一题背后挂着哪个模板、哪个套路、哪类思路以及它们之间怎么互相迁移。这篇文章就是围绕这些展开的后面我会按专题、套路、周赛复盘、避坑指南和长期维护这几个角度来写并且会一直持续更新下去。1. 先别急着刷题把 Hot 100 拆成一套体系1.1 我的分类方法我第一次刷 Hot 100 的时候就是老老实实从第 1 题“两数之和”开始按题号一路往下刷。刷到第 30 题左右就明显不对劲了今天做回溯、明天做动态规划、后天又跳回链表知识在脑子里是一粒一粒的串不起来。后来我停下来重新做了一件事——把这 100 道题按“数据结构”和“算法思想”两个维度重新分类整理完才发现很多东西是重复的。我自己的分类结果大致是这样的强调一下这是我自己粗暴统计的不是官方数据分类感觉占比典型代表数组 / 哈希表比较重约 20 道以上两数之和、三数之和、字母异位词分组、最长连续序列双指针 / 滑动窗口约 15 道左右无重复字符最长子串、盛最多水的容器、最小覆盖子串链表约 8 道反转链表、环形链表、排序链表、LRU 缓存二叉树 / 递归约 15 道二叉树中序遍历、验证二叉搜索树、二叉树的最近公共祖先回溯 / DFS约 10 道全排列、组合总和、单词搜索、岛屿数量动态规划约 15 道爬楼梯、打家劫舍、零钱兑换、编辑距离、最长递增子序列栈 / 队列 / 单调栈约 8 道有效的括号、接雨水、柱状图中最大的矩形、基本计算器贪心 / 其他剩下跳跃游戏、买卖股票时机、多数元素为什么一定要先做这个分类因为刷题最怕的是“假装努力”——每道题都看题解看懂了关上屏幕全忘了。分类之后你会发现100 道题其实可以被压缩成十几个模板。比如“岛屿数量”“单词搜索”“课程表”本质都是 DFS/BFS而“接雨水”“柱状图中最大的矩形”共用单调栈思想“爬楼梯”“打家劫舍”“最长递增子序列”全都是“先定义 dp[i]再找转移关系”。题目会变模板不会变这才是刷题能形成复利的原因。1.2 按专题刷而不是按题号刷把题目分类之后我的刷题顺序就彻底改了。我不再按题号顺序而是按专题逐块推进先集中刷数组和哈希表再刷双指针、滑动窗口之后是链表、二叉树、回溯、动态规划。每个专题刷完我会停下来把这一组题的共性、差异、边界陷阱写成一篇笔记然后才开始下一个专题。这样刷有一个明显的好处大脑会在短时间反复调取同一类套路。比如你连续三天都在做滑动窗口类的题目再遇到“最小覆盖子串”的时候你会本能地想到维护 left 和 right 两个指针而不是在暴力枚举里打转。专题内横向对比还能帮你看到“变体”是怎么从“原题”长出来的无重复字符最长子串是“求最长”最小覆盖子串是“求最短”一个收缩窗口时机不同但骨架是一模一样的。如果你也准备这么干我建议每个专题控制在 3 到 5 天每专题刷完做一次“套路复盘”这个专题有哪几种变化你最怕哪种变化下次见到类似题第一步应该固定做什么这一步比多刷 5 道新题更有价值。2. 四个高频套路原型与 Hot 100 代表题2.1 二分答案从“爱吃香蕉的狒狒”讲起Hot 100 里那道“爱吃香蕉的狒狒”LeetCode 875原题名其实是 Koko Eating Bananas中文圈经常被叫成狒狒或者珂珂是我特别想拿出来讲的一道题因为它是“二分答案”这个套路最经典的入门题。题目其实很简单有 N 堆香蕉每堆有 piles[i] 根Koko 每小时最多吃一堆、且只吃 k 根如果吃不完这一堆剩下的留到下一小时接着吃。现在要在 h 小时内吃完求最小速度 k。很多人看到这题第一反应是直接从小到大枚举 k从 1 开始试到 max(piles)然后检查每个 k 是否能在 h 小时内吃完。这样当然能做但复杂度是 O(maxPiles * n)数据一大就超时。正确解法是二分答案速度 k 的可行范围是 [1, max(piles)]check 函数就是“用当前速度 k 吃掉所有香蕉需要多少小时”然后看总小时数是否小于等于 h。check 函数里最关键的细节是“每小时最多吃一堆”怎么算时间。对于一堆有 p 根香蕉用速度 k 吃的话耗时是 ceil(p / k)也就是向上取整。代码里不要用浮点数直接用整数运算def can_finish(piles, k, h): hours 0 for p in piles: hours (p k - 1) // k # 等价于 ceil(p / k) return hours h def minEatingSpeed(piles, h): left, right 1, max(piles) while left right: mid (left right) // 2 if can_finish(piles, mid, h): right mid else: left mid 1 return left这套“二分答案”的模板能套到一堆题上LeetCode 1011“在 D 天内送达包裹的能力”传运货船、410“分割数组的最大值”、774“最小化去加油站的最大距离”等等。它们的共同特征都是“求某个单调量的最值”而且这个量可以直接被 check 出来。我强烈的建议是把这道题的代码和思路存成一个独立模板以后遇到“最小化最大值”“最大化最小值”这一类的描述第一反应就应该往二分答案上靠。二分答案的易错点有两个这里提前说一个是边界一般用左闭右开或者左右都闭的写法我上面给的是左闭右闭循环条件写成 left right注意 mid 取左中位数否则死循环另一个是 check 函数里的计算顺序一定要用整数向上取整不要写浮点然后四舍五入精度问题会让你在隐藏测试用例上翻车。2.2 双栈处理表达式基本计算器系列LeetCode 224“基本计算器”和 227“基本计算器 II”是 Hot 100 里看起来偏硬核、实际套路非常固定的题。224 的规则是只有加减和括号227 则是没有括号但有加减乘除。很多同学第一次见到这种题心里发怵其实核心就是“双栈法”一个栈存数字、一个栈存运算符从头扫描字符串遇到数字就累计遇到运算符就处理优先级遇到左括号就入栈、右括号就弹栈。我自己实现的时候特别喜欢一种简化写法——把减法当成“加一个负数”这样数字栈里存的就全是待加的值。具体思路是维护一个 res 表示当前括号内的累计结果维护一个 sign 表示当前数字前的符号维护一个 stack 用来保存遇到左括号之前的 res 和 sign。扫描的过程大致是这样def calculate(s): stack [] res 0 num 0 sign 1 i 0 while i len(s): ch s[i] if ch.isdigit(): j i while j len(s) and s[j].isdigit(): j 1 num int(s[i:j]) i j continue if ch : res sign * num num 0 sign 1 elif ch -: res sign * num num 0 sign -1 elif ch (: stack.append(res) stack.append(sign) res 0 sign 1 elif ch ): res sign * num num 0 res * stack.pop() # 恢复括号前的符号 res stack.pop() # 恢复括号前的累计结果 i 1 res sign * num return res这段代码看着有点绕其实每一步都对应着一个很朴素的道理加减法只有符号在变化括号只是暂时把结果存起来右括号再取回来继续运算。等你想明白“把减法变成加负数”之后再看 227 题的乘除法就好处理多了——乘除法优先级高遇到 * 或 / 就把栈顶元素和新数字算完再压回栈最后把栈里所有数加起来。这就是“处理优先级”的直观解法。这套题的易错点特别值得记录输入里的空格一定要跳过多位数字不能只读一位负数比如“-21”开头如果没处理会错常见技巧是在前面补一个 0括号内的符号会在右括号处翻转这是最容易漏的地方。我建议你把这题和 150“逆波兰表达式求值”放在一起学三者配合吃透以后面试里遇到表达式类的题就能坦然很多。2.3 滑动窗口的固定骨架滑动窗口是 Hot 100 里出现频率极高的一个套路代表题有无重复字符的最长子串、最小覆盖子串、字符串的排列等。这类题有个共同描述在一个数组或者字符串上求满足某条件的“最长/最短/固定长度”区间。暴力做法通常是枚举所有子区间O(n^2)滑动窗口的核心就是用两个同向指针维护一个窗口在移动右指针扩展窗口的同时判断是否需要移动左指针收缩从而把复杂度降到 O(n)。我习惯把滑动窗口模板写成这样def sliding_window(s): n len(s) window {} left 0 right 0 ans 0 while right n: c s[right] window[c] window.get(c, 0) 1 right 1 while 需要收缩: d s[left] window[d] - 1 left 1 ans max(ans, right - left) return ans真正的难点不在模板而在“什么条件下收缩窗口”。拿“无重复字符的最长子串”举例维护一个哈希表记录字符出现次数当 window[c] 1 说明有重复就不断 left 右移直到 window[c] 变回 1这种情况下收缩条件是“当前字符出现次数大于 1”再看“最小覆盖子串”收缩条件是“窗口内已经完整覆盖了目标串的所有字符”而且收缩过程中还要不断尝试更新最短长度。两题共用一套骨架只在收缩条件上不同。滑窗最容易犯的错是只记得写 right 指针的扩展忘记写收缩或者收缩之后忘记同步更新答案。另一个常见问题是“窗口合法性判断太慢”比如最小覆盖子串每次都要判断是否覆盖最好用一个 remain 计数器来维护“还差几个关键字符没凑齐”而不是每次全量扫描哈希表。我在刷第二轮的时候把这道题重新用 remain 优化了一遍速度提升非常明显面试时写代码也会简洁很多。2.4 动态规划先把状态定义说清楚动态规划在 Hot 100 里的占比不小而且很多人口中的难题都集中在 DP 上。其实动态规划翻来覆去就是三件事状态定义、状态转移方程、初始化和遍历顺序。我见过太多人卡在 DP 上倒不是因为不会写转移方程而是状态定义根本没想清楚就开始套公式结果后边全乱。拿 Hot 100 里简单的“爬楼梯”说dp[i] 表示“爬到第 i 阶的方法数”转移方程是 dp[i] dp[i-1] dp[i-2]因为最后一步要么跨一阶、要么跨两阶。拿“打家劫舍”说dp[i] 到底表示“偷到第 i 家时的最大金额”还是“第 i 家偷不偷的状态”直接决定了整个代码的写法。更复杂一点的“最长递增子序列”dp[i] 不能定义为“前 i 个元素的最长递增子序列长度”而必须定义为“以 nums[i] 结尾的最长递增子序列长度”如果不加这个“以 i 结尾”的限定后续转移根本无法推进。我刷 DP 专题的经验是每题先别急着写代码拿一张纸把“dp[i] 代表什么”“答案应该取哪个格子”“边界条件是什么”写下来写清楚再动键盘。如果状态定义写得漏洞百出代码一定也是错的。Hot 100 里有几个 DP 题值得反复钻研包括 198 打家劫舍、322 零钱兑换、300 最长递增子序列、72 编辑距离、124 二叉树中的最大路径和。“零钱兑换”还牵扯到一个“求最小值”的初始化问题dp 数组要初始化为正无穷再设 dp[0] 0这个细节新手几乎必踩。DP 和前面几个套路最大的不同是它没有一劳永逸的万能模板但你练多了会发现大多数题的“状态定义”都逃不开“以某个位置结尾”“某个区间内”“某种约束条件下”这几类句式。能把状态定义用一句话说清楚这道题基本就做完一半了。3. 用一场周赛给“持续更新”做体检3.1 周赛 430 当成阶段自测这篇总结的标题里写了“持续更新”我最近一次用它推动自己前进的方式就是拿 LeetCode 周赛 430 做了一次阶段自测。周赛每周一场四道题难度通常从简到难递进正好可以检验你从 Hot 100 里提炼的模板到底管不管用。第 430 场周赛给我的直观感受是前两题如果平时专题刷得扎实基本能在 30 分钟内解决第三题开始考验思路转换不是你背过原题就能秒的第四题就直接拉开差距了涉及的往往不是单一套路而是多个知识点组合。我印象很深的是其中一道题让我想到 Hot 100 里的某个模板但需要进一步改造那一刻我才意识到把模板背熟只能算入门能认出“这题是哪个模板的变体”才是刷题的核心能力。周赛的另一个价值是“限时”。平时刷 Hot 100我经常一道题磨两小时虽然也做出来了但面试根本没有这种宽松环境。周赛每道题 20 到 30 分钟的压力会让你暴露出真实水平是卡在读题、卡在边界、卡在手写代码还是卡在思路这些信息比多刷十道题更值钱。3.2 赛后复盘的四张表每次周赛结束我都会花半小时做一次结构化复盘绝对不写完代码就丢到一边。我会整理四样东西答题时间线、卡点记录、错因分类、可迁移模板。答题时间线就是记录每道题用了多久卡点记录是写清楚自己“卡在哪个位置”是读题看不懂、边界条件没想到还是某个语法写错错因分类是横着比较自己常犯的错误是数学推导偏弱、还是字符串处理总漏空格可迁移模板是找出这道题对应 Hot 100 里的哪个原型以后出现类似题应该优先想到什么。这里给一个我常用的复盘表结构你可以直接抄题号用时卡点描述错因类型可迁移的 Hot 100 模板T118 分钟边界数组为空时没考虑边界处理二分答案 / 哈希表T226 分钟状态转移少了一种情况DP 状态定义打家劫舍 / 零钱兑换T3超时没认出单调栈特征套路识别接雨水 / 柱状图最大矩形T4未做出多知识点组合不会拆综合拆解树 DP 组合这个复盘表坚持三个月你会非常清晰地看到自己的薄弱点在哪里而不是稀里糊涂地“刷了很多题”。尤其是“错因类型”这一列如果总出现“边界处理”你就专项去练边界用例如果总出现“套路识别”你就回去重刷对应专题。每周一场一年 50 场等于给自己做了 50 次免费的模拟面试而且这些数据全是你自己的真实表现比任何机构的水平测试都可信。4. 从 Hot 100 到面试实操路线与避坑指南4.1 刷题顺序怎么排如果你想照着这份总结动手我建议按这个顺序来先刷数组/哈希表再刷双指针和滑动窗口然后链表接着二叉树和回溯再图论 BFS/DFS最后动态规划和贪心。为什么这么排因为前几个专题相对容易上手能建立信心二叉树和回溯又会反复用到递归思想练完递归再碰动态规划你会更容易理解“状态转移”其实也是一种递归关系的迭代表达。数组和哈希阶段可以做两数之和、三数之和、字母异位词分组、最长连续序列、最大子数组和。双指针滑动窗口阶段做无重复字符最长子串、盛最多水的容器、三数之和、最小覆盖子串。链表阶段做反转链表、环形链表、合并两个有序链表、LRU 缓存。二叉树阶段做二叉树中序遍历、验证二叉搜索树、二叉树的最近公共祖先、从前序与中序遍历序列构造二叉树。回溯阶段做全排列、组合总和、子集、括号生成。图论阶段做岛屿数量、课程表。动态规划阶段做爬楼梯、打家劫舍、零钱兑换、最长递增子序列、编辑距离。贪心与其他做跳跃游戏、买卖股票的最佳时机。这个顺序不是唯一的但核心思想是“由易到难、专题集中”。我不建议一上来就死磕动态规划和编辑距离那样大概率会被劝退。先把简单专题吃到手再一步步扩大舒适区这样“持续更新”才可持续。4.2 写题解的固定格式我从第二轮开始要求自己每道 Hot 100 题都必须写题解而且格式固定题目一句话描述、我的思路、代码、复杂度、需要注意的坑、可迁移的模板。为什么要写题解因为“看懂了”和“能讲清楚”是两码事。如果你能把自己的思路写成一篇别人看得懂的题解那这道题基本才算真正属于你。我的题解格式长这样题目一句话求 xxx条件是在 O(n) 时间内。思路先想暴力怎么做再说明优化爆发点在哪里。这一步很关键不要只贴最优解最好把“从暴力到优化”的思考路径写出来。代码用注释标清楚核心步骤。复杂度时间和空间都要写。坑记录自己在这道题上犯过的错。模板从中抽出的套路能否迁移到别的题上。写题解最大的收益发生在几周后。当你在周赛或新题里发现“可以用我之前那篇题解的套路”时那种感觉比 AC 还爽因为你验证了自己的方法论真的有用。持续更新这一系列文章我靠的就是这个习惯。4.3 我踩过的坑和补救方法刷 Hot 100 这一年我踩过的坑大概可以列成几类每类都有血泪教训。第一类是“只看题解不复现”。第一轮刷题的时候碰到难题我常常看一眼题解觉得“会了”结果第二天重写代码磕磕绊绊写不出来。后来我给自己定了一个死规矩题解可以看但看完必须合上答案自己从头到尾写完并 AC否则不算刷过这道题。这个规矩很笨但非常有效。第二类是“边界条件只靠猜”。比如二分答案的边界到底是 left right 还是 left right滑动窗口收缩时 left 移动一步还是多步这些细节不靠推理、靠试结果就是经常在隐藏用例上翻车。我的补救方法是每道题写完代码后强制自己想三种测试用例空输入、只有一个元素、极端值。尤其是涉及数组区间、字符串索引的题提前把这些边界在纸上画出来能省大量调试时间。第三类是“会 AC 但不会讲”。我第一轮刷完去模拟面试发现自己虽然能写出 AC 代码但被面试官追问“这个时间复杂度为什么是 O(n)”的时候解释得很混乱。后来我在写题解时强制自己把复杂度推导演算写完整要算每个循环最多执行多少次、为什么均摊是 O(1)这样才能真正过关。面试和刷题最大的区别就是面试官要看到的是你的思考过程而不是最终那个 Accepted。第四类是“忽视空间复杂度”。很多人只盯着时间但像“最小覆盖子串”这类题空间复杂度如果用了额外数组还说得通可有些题会明确要求 O(1) 空间比如“多数元素”的最优解法 Boyer-Moore 投票空间 O(1)如果你只会哈希表面试就容易被追问到卡壳。刷题的时候刻意给自己加要求能不能把空间压下来多问一句进步就多一点。5. 把“持续更新”做成长期工程5.1 更新节奏与进度管理既然标题说了“持续更新”那我就讲讲我如何把一份刷题总结做成长期运转的“工程”而不是三天打鱼两天晒网。我的做法很简单每周固定刷固定量比如工作日每天一道新题、周末做一场周赛复习每两周写一篇阶段总结。这个节奏听起来不快但一年下来就是 300 多道题的量级足够覆盖面试高频范围。进度管理我用一个表格维护每次做完一题就更新一行字段包括题号、题名、难度、分类、第一遍是否独立 AC、复习次数、笔记链接。这种表格最大的好处是“可视化”——看着未 AC 的数量越来越少你会有真实的正反馈动力同时它也是一份复习清单隔一段时间回头看哪些题已经忘光了一目了然。题号题名分类第一遍独立 AC复习次数备注1两数之和哈希表是2可作为模板3无重复字符最长子串滑动窗口否看题解1收缩条件需重点复习875爱吃香蕉的狒狒二分答案是2模板题用于周赛表格不必多复杂关键是坚持更新。你甚至可以把它放到自己的博客、笔记软件或者本地仓库里成为“长期资产”。每次周赛遇到的错题也追加进去配上和 Hot 100 的关联这样你的刷题系统会越来越庞大但仍保持组织性而不是堆了一堆散题。5.2 刷三轮之后的体会最后分享一点个人体会。我第一轮刷 Hot 100 是“求数量”每天逼自己刷三题结果到了后半程难题看的比做的多效果很差第二轮是“求质量”只刷第一轮里没 AC 的题和自己归类出的薄弱专题配合写题解进步快很多第三轮我已经不太需要刷原题了更多是翻自己的笔记和周赛错题把模板反复“过电影”。刷三轮之后最明显的感受是面试时拿到新题不再慌张因为能在几分钟内把新题映射到熟悉的模板上。比如看到“求最小值中的最大值”我会自然想到二分答案看到“窗口内求最短覆盖”我会自然想到双指针哈希计数看到“配对括号”或“表达式求值”我会直接想到栈。这种直觉不是天生的是大量重复和复盘逼出来的。“持续更新”这四个字对我来说不只是一篇博客的标题它代表一种长期主义算法能力没有一劳永逸的终点只有不断在 Hot 100、周赛、错题、新题之间来回穿梭才能把刷过的每一道题转化为真正的底牌。这篇文章我也会随刷随改后面还会补上更多专题的详解和具体题目的拆解记录希望对正在啃 LeetCode 的你有一点实在的帮助。
返回列表