☰
LeetCode Hot 100刷题方法论万字总结:专题分类、套路模板与周赛复盘
2026/10/1 11:01:23 网站建设 项目流程

刷 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 题的乘除法就好处理多了——乘除法优先级高,遇到 * 或 / 就把栈顶元素和新数字算完再压回栈,最后把栈里所有数加起来。这就是“处理优先级”的直观解法。

这套题的易错点特别值得记录:输入里的空格一定要跳过;多位数字不能只读一位;负数(比如“-2+1”)开头如果没处理会错,常见技巧是在前面补一个 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 的你有一点实在的帮助。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询