我不打算再堆一遍“Hot 100是什么”这类你搜一下就知道的废话。直接说结论:LeetCode Hot 100这份题单,是目前算法面试高频题最浓缩的一份清单。前50题基本是基础套路大礼包,后50题开始出现综合性设计。我这两年带人刷题,不管对方是科班还是转码,用的都是同一套打法:先把题单按数据结构切块,再用固定模板吃透每一类,最后用复盘把“卡住的地方”变成自己的直觉。这篇文章我就按这个顺序完整拆一遍,环境、套路、计划、排坑都会讲到。
1. 先看懂Hot 100:这是刷题计划表,不是题库
很多人上来就在LeetCode页面点开Hot 100,从第1题“两数之和”开始按顺序刷。这是个极其常见的误区。Hot 100并不是按照难度递增排列的,如果你真的从1到100顺着刷,大概率会在第30题左右被各种综合题打得怀疑人生,然后弃坑。
1.1 题单的整体结构:前50题练套路,后50题考综合
Hot 100的题目来源是大量真实笔试、面试的高频考题统计,所以它天然带着“考点权重”。我把它粗略分成两段:
- 前50题:数组、链表、字符串、哈希、双指针、滑动窗口、基础动态规划、基础回溯。每一道题背后都是一个可以迁移的套路,难度集中在“中等”,适合建立肌肉记忆。
- 后50题:二叉树、图、堆、前缀树、进阶DP(编辑距离、正则表达式匹配)、设计型题目(LRU缓存、前缀树实现、用栈实现队列)。这些题往往要叠加两到三个基础技巧,或者查考你对数据结构本身的理解。
所以我的建议是:不要从1刷到100,先把题目按“数组/字符串”“链表”“树”“图”“动态规划”“回溯”“堆/栈/队列”分类,再从每类里挑3到5道经典题连续打通。连续练同一个套路,比一天换一个题型效率高太多。
1.2 为什么偏偏要选Python来刷
用Python刷Hot 100,最核心的原因是表达效率。算法面试本质是考察你“能否在半小时内把模糊思路变成可运行代码”。Python的语法非常接近伪代码,能让你把大脑算力集中在逻辑设计上,而不是花在指针类型、内存释放这些细节上。比如统计频率用collections.Counter,键值对默认值用defaultdict,BFS队列用deque,这些C++要写一大堆的活,Python一行搞定。
但这里必须说清楚一个反向代价:Python的执行效率远低于C++/Java,在LeetCode的极限用例下更容易超时。所以你不能依赖Python的“高级特性”去作弊。比如有人用切片翻转字符串、用all()找出所有组合来暴力过关,这样刷题是刷了个寂寞。你得主动给自己加限制:能用O(n)的不要写O(n²),该剪枝的一定要剪枝。面试官也不是傻子,他们会追问复杂度,你答不上来代码过了也没用。
2. 刷题前的环境准备:装对Python,配好调试环境
刷题这件事,环境问题比算法问题更容易劝退新手。我见过太多人最后不是卡在题目上,而是卡在“明明装了Python,vscode却跑不起来”。这一节先把环境彻底理清楚。
2.1 Python安装与vscode配置里最常见的坑
Windows安装Python时,最容易踩的坑是漏掉Add Python to PATH。安装包打开后第一屏就有这个勾选项,默认是不勾的,你不手动勾上,装完在终端输python就是“不是内部或外部命令”。macOS和Linux用户则要注意权限问题,建议从官网下载安装包,不要用系统自带的旧版本导致语法特性跟不上。
装完Python之后,vscode里还要装一个Python扩展插件。装完插件后再按Ctrl+Shift+P,输入Python: Select Interpreter,选到你刚装的解释器路径。很多人没做这一步,导致vscode不知道你用的是哪个环境,import numpy这种命令报错或者完全没有智能补全。这个操作每个新环境都要做一次,不是装一次就一劳永逸。
另一个高频坑是环境管理混乱。我见过有人电脑上同时装了官网Python、Anaconda、Microsoft Store版Python,三个环境版本各不相同。你用终端输pip装的库,装到的是系统Python,而vscode里跑的是Anaconda的虚拟环境,import自然失败。解决思路很简单:先用where python(Windows)或which python(macOS/Linux)确认当前用的是哪个环境,再想清楚你到底要让哪个环境承载刷题。如果只是刷题,一个干净的环境完全够用,别把自己搞成运维。
2.2 最值得记住的几个标准库
Hot 100里Python能发挥最大威力的标准库就几个,按我自己的使用频率排序:
collections.Counter:统计频率、判断互为字母异位词,一行搞定。collections.defaultdict:建邻接矩阵、构建图,省去“key不存在先初始化”的样板代码。collections.deque:BFS和滑动窗口的御用队列,双向操作效率高。functools.lru_cache:递归函数上面加一个装饰器,立刻获得记忆化效果,普通DFS瞬间变成DP。heapq:Top K问题、合并K个有序链表、找中位数,堆操作全用它。bisect:有序数组定位插入位置,二分查找问题节省大量手写代码。
用这些库不叫投机取巧,它们本身就是标准库,面试官认可。但问题是你要说得清复杂度。打个比方,你在写题时用heapq.nlargest(k, nums),看起来一行就解决了Top K,但面试官追问“这个函数的复杂度是多少”,你要是答不上来,那就是这次面试的红灯。用库的前提是“你不用库的大白话写法也能手写出来”。
2.3 本地调试骨架:让每道题都有回归用例
我强烈建议不要只在LeetCode网页IDE里写题。网页IDE提交方便,但没有断点、没有变量观察窗,排查边界条件效率极低。我的做法是在本地vscode里维护一个模板文件,每个题目都转成一个可运行脚本,开头带一组自测用例。比如最经典的两数之和:
from typing import List def two_sum(nums: List[int], target: int) -> List[int]: seen = {} for i, num in enumerate(nums): if target - num in seen: return [seen[target - num], i] seen[num] = i return [] if __name__ == "__main__": test_cases = [ ([2, 7, 11, 15], 9, [0, 1]), ([3, 2, 4], 6, [1, 2]), ([3, 3], 6, [0, 1]), ] for nums, target, expected in test_cases: res = two_sum(nums, target) print(res, "OK" if res == expected else f"FAIL, expected {expected}")每次改完逻辑,跑一遍这个文件,所有用例立刻验证。这一套流程看着简单,但能让你的刷题效率翻倍。如果出错,直接用断点或者print(i, left, right)这种关键变量打印,几秒钟就能定位问题,不用在网页上一次次提交浪费机会。
3. 按套路拆解Hot 100:四类高频题型一次打通
Hot 100最让我喜欢的一点是:里面的题目套路高度重复。你刷到后面会发现,所谓新题不过是旧模板换了层壳。下面我按四类核心题型,把最实用的套路拆开讲。
3.1 数组与指针题:哈希、双指针、滑动窗口连招
数组和字符串是Hot 100的开篇主力,几乎必考三类技巧:哈希、双指针、滑动窗口。这三者是同一个思想的不同变种:用较少的遍历次数换取额外的空间或已知信息。
先说哈希。两数之和是最经典的入门题,核心思路是“边遍历边存,边存边找”:每拿到一个数字,看哈希表里有没有它要的“另一半”,有就返回,没有就把它自己存进表里。这样只用一次遍历,时间复杂度O(n)。同思路的题还有字母异位词分组、最长连续序列。
双指针则用在有序或“数组两端”的问题上。典型题是三数之和:先排序,固定一个数,剩下两个数用左右指针从两端向中间走,因为有序,就能用“大了左移、小了右移”的方式逼近目标。去重是这道题的核心难点,一定别忘了跳过重复元素。
滑动窗口是处理“连续子串/子数组”问题的神器。理解的要点是:右指针负责“扩展窗口”,左指针在条件不满足时“收缩窗口”。以无重复字符的最长子串为例:
def length_of_longest_substring(s: str) -> int: seen = set() left = 0 ans = 0 for right, ch in enumerate(s): while ch in seen: seen.remove(s[left]) left += 1 seen.add(ch) ans = max(ans, right - left + 1) return ans这个模板背下来,后面像最小覆盖子串、水果成篮、找到字符串中所有字母异位词全部可以套同一骨架。区别只在于“窗口内什么时候满足条件”这个判断逻辑不同。
3.2 链表与树:递归与迭代的平衡
链表题在Hot 100里占块头不小:反转链表、环形链表、合并两个有序链表、相交链表、回文链表、LRU缓存。链表的麻烦之处在于指针操作容易写乱,但核心动作其实就一组:缓存后继、改向、移动。
以最经典的反转链表为例,迭代写法的骨架是三个指针:
def reverse_list(head): prev, cur = None, head while cur: next_node = cur.next # 先缓存后继 cur.next = prev # 指向前驱 prev = cur # 前驱前移 cur = next_node # 当前前移 return prev树的题目则围绕递归展开,因为树本身就是天然的递归结构。前序、中序、后序遍历的差别只是“访问当前节点”的位置不同;层序遍历必须用队列,核心操作是“一次取完当前层的所有节点”。套路非常固定:处理当前节点,递归处理左子树,递归处理右子树。唯一容易忽略的是空节点判断和返回值设计,先想清楚“空的时候返回什么”、“递归结果怎么向上传”。
3.3 动态规划:别背公式,先学会四步推导
动态规划是Hot 100里最难啃也最重要的一块。很多人一上来就看状态转移方程,然后一头雾水。正确的姿势是先举一个小例子,手动推一遍,再总结出“每个位置怎么从前面的位置算出来”,最后才写代码。
我用的四步法是:
- 定义状态:
dp[i]表示什么?必须一句话说清,说不清就是还没想明白。 - 写递推关系:
dp[i]和前面的状态怎么关联? - 初始化:数组的起始状态是什么?
- 确定遍历顺序:是从左到右、从右到左,还是二维的按行按列?
以最大子数组和为例,一句口诀是“到当前位置为止的连续子数组最大和,要么是前面的最大和加上当前数,要么是当前数自己重新开一局”:
def max_subarray(nums): dp = nums[:] # 初始化为原数组 for i in range(1, len(nums)): dp[i] = max(dp[i - 1] + nums[i], nums[i]) return max(dp)这里的dp数组还可以优化成两个变量滚动更新,空间复杂度从O(n)降到O(1)。面试时主动提到这个优化,是很加分的细节。
爬楼梯、打家劫舍都是这个模式。编辑距离、最长回文子串则是二维DP,递推关系写起来更复杂,但步骤完全一致。坚持用四步法,别一上来就背方程,DP才能真正变成你自己的技能。
3.4 图与回溯:DFS“感染”法和回溯模板
图论和回溯题在Hot 100里绝不缺席,尤其是岛屿数量、全排列、组合总和、括号生成、单词搜索、课程表。回溯的模板几乎可以套所有“求所以可能解”的问题:
def backtrack(path, choices): if 满足结束条件: ans.append(path[:]) # 注意拷贝,防止后续修改影响结果 return for c in choices: if c 不合法: continue path.append(c) # 做选择 backtrack(path, choices) path.pop() # 撤销选择全排列是回溯最标准的例证,难在加一个visited标记已用数字。组合总和则是排序+剪枝,递归前先把候选排序,一旦当前和超过目标就提前返回。括号生成则利用“左括号数必须大于右括号数”这个约束剪枝。
岛屿类的图题有个非常经典的“感染”技巧:遍历到一块“陆地”时计数加一,然后用DFS或BFS把相邻的所有陆地都改成“水”。这样下次遍历就不会重复数:
def num_islands(grid): def dfs(i, j): if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]) or grid[i][j] != "1": return grid[i][j] = "0" # 感染:标记为已访问 for di, dj in ((1,0), (-1,0), (0,1), (0,-1)): dfs(i + di, j + dj) count = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == "1": count += 1 dfs(i, j) return countBFS则用来处理“最短路径”类问题,比如单词接龙。朴素BFS是从起点一步步扩展,走完整个搜索空间;双向BFS则是同时从起点和终点向中间扩展,能大幅缩小搜索空间。代码写起来更复杂一些,但在图上大时收益非常明显。
4. 这样定刷题计划,才不会三天打鱼两天晒网
再好的题单,没有节奏也刷不完。刷Hot 100这件事,真正难的不是题目,而是“持续”。我推荐一个三轮刷题法,每一轮的目的不同,难度也不同。
4.1 三轮刷题法:分类集训、限时盲打、口述思路
第一轮:分类集训,持续3到4周。先按数据结构分类,每天只刷同一类题型。比如这一周专攻数组双指针,下周专攻链表,再下周动态规划。每天一到两道“中等”难度题即可,重点是把模板打熟。这个阶段不要碰难题,否则容易产生挫败感。
第二轮:随机盲打,持续2到3周。题型混合着来,模拟面试限时45分钟一道。写不出来就老实承认,去看题解,然后当场复盘。这轮的目的不是“做出多少题”,而是让大脑适应“解不出题时的压力”。很多时候面试挂不是不会做,而是高压下脑子一片空白。
第三轮:口述思路,持续1到2周。只看题目描述,不打开编辑器,先说出:这题属于什么类型、用什么数据结构、最终时间复杂度是多少。说得出来再动手写代码。这个阶段你会发现自己经能识别出大部分题目的“骨架”,看到题目自动联想到对应套路,这就是真正质的飞跃。
我建议的每周节奏大概是这样的:
| 阶段 | 目标 | 每日量 |
|---|---|---|
| 第一轮 | 熟悉套路,建立肌肉记忆 | 1~2道,分类刷 |
| 第二轮 | 适应面试节奏,训练抗压 | 1道限时 + 复盘 |
| 第三轮 | 形成条件反射,抓题目本质 | 2~3道口述,选1道写码 |
4.2 复盘记录模板:把“卡住的原因”变成码力
刷题不复原等于白刷。这里的复盘不是让你把题解抄一遍,而是记录“你卡在哪里”和“最优解的哪个一步戳破了你思维里的那层窗户纸”。我用的复盘模板是:
题目名: 我的解法(一句话): 卡点(哪一步卡住/想了多久): 最优解的关键一步: 时间复杂度(我的 vs 最优): 一句话教训:举一个我最常见的例子。刷两数之和时,很多人卡点是“下意识用两个嵌套循环,想不到哈希存差值”。教训就记成一句话:“看到‘找出两个数满足条件’,第一时间想哈希。”这条教训两句话都算不上,但当你刷到最小的k个数、连续子数组和这种变体题时,这句话会第一时间弹出来指导思路。用Excel或者Notion拉一张表,坚持记录三四周,你会发现同一类坑你不会踩第二次。
5. 我踩过的坑,直接给你一份排查清单
刷题过程中的报错、超时、内存溢出,很多和算法无关,而是环境或编码习惯的问题。这一节总结我见过最多、也最容易被“新手下病根”的坑。
5.1 环境、依赖和PATH的连锁反应
Python环境问题最典型的三个表现:命令行输python没反应、import numpy报ModuleNotFoundError、vscode运行代码时解释器环境不对。这些问题的根源几乎都是“没有先确认哪个Python在执行”。
排查顺序应该固定为:先在终端输where python/which python确认当前解释器路径;再确认vscode里选到的解释器跟终端一致;最后在解释器里跑pip list看包到底装没装上。很多人因为电脑上多个Python版本并存,导致“装了numpy却到处都找不到”,本质上就是这个顺序没理清。遇到pip install超时或者下载特别慢的情况,换成国内镜像源,指定-i参数就能解决,核心库安装问题90%都是环境路径问题,不是代码问题。
5.2 超时、内存溢出与边界错误的定位
LeetCode报Time Limit Exceeded,最可能的原因有三个,按频率排序:
- 嵌套循环做了无意义的重复计算,典型表现是复杂度O(n²)硬扛大数据量;
- 递归没有记忆化,同一个子问题反复计算,指数爆炸;
- 循环里做了复制、切片、字符串拼接之类的低效操作。
遇到TLE时,先别急着优化细节,先问自己一句:这个算法本身的复杂度是不是最优?如果不是,先换算法再去微调。
Memory Limit Exceeded则多发生在几个场景:一是二维DP矩阵内存超出了题目限制,这时应该考虑滚动数组;二是递归深度太深造成栈溢出,RecursionError报错很明显,可以用sys.setrecursionlimit()临时解决,但根本办法还是转成迭代或用循环。还有一类容易被忽略的:递归函数里的可变默认参数。比如def dfs(path=[])这种写法,多个递归路径会共享同一个列表,行为完全不可控。正确做法是在函数内部用局部变量初始化。
边界错误则几乎全部来自三种情况:空输入没考虑、下标从0还是从1没想清楚、初始化值不对。我每次提交前都会用一个自查清单过一遍:空值、单元素、全相同、全逆序、长度很长、数字很大。按这个清单逐一验证,能拦下一大半的边界问题。
5.3 调试习惯和几个容易写错的细节
调试时我建议用“二分定位法”:先把报错分成“逻辑错”和“边界错”两类,再用打印或断点缩小到具体循环。打印中间变量时,格式一定要带上下文,比如print(i, left, right, cur_sum),比一个裸print(res)更容易看出变量间的关系。你可以在本地编辑器里加断点看执行流程,比在刷题平台上反复提交给评测系统强太多。
一个特别容易被忽略的细节是“结果需要拷贝”。回溯模板里记录答案时,如果直接把path塞进结果列表,后续path.pop()会把已经记录的答案一起改掉。必须写成ans.append(path[:]),对Python新手而言这是第101个坑。
还有一个细节是关于调试原则:不要在网页上心里默写一遍就盲目提交,先用本地的多用例骨架跑一遍,跑通了再贴到网页提交,能省下一大堆提交上限。LeetCode编辑器本身没有“本地运行”按钮,所以本地这套骨架的价值就在这里。
我个人带人刷完几轮Hot 100之后最大的体会是:题单本身不重要,重要的是你通过它获得的“解题直觉”。题目千变万化,但套路始终就那几十个,无非是哈希、双指针、滑动窗口、递归、回溯、DP、BFS这些组合。你真正刷完一遍之后,再遇到新题,会自动在脑子里把它归类,然后从对应模板里挑一个往里面套。这种条件反射不是靠背题得来的,是靠“连续几天刷同一个套路 + 每天记一条卡点教训”攒出来的。最后给一个小建议:不要追求一天刷十道题。保持每天一到两道的稳定节奏,配合你那套复盘表格,两个月后回头你会发现自己写题的速度和底气完全不一样了。