
我刷了三年LeetCodeTop100这100道题被我反复过了好几遍现在回头看认真说一句如果你只能刷一套题那选Top100就够了。它不是简单把热门题堆在一起而是把面试里最高频的考点、最典型的数据结构套路、最能拉开差距的思维模型全浓缩进了这100道题里。不管是准备春招秋招还是像我一样半路转码想补算法短板这100道题都能给你一个清晰的坐标。下面我把自己的刷题路线、核心题型拆解、以及几道经典题的完整实操过程都摊开讲希望能帮你少走点弯路。1. 聊聊Top100这100道题到底值不值得刷1.1 Top100是怎么来的含金量有多高LeetCode上的Top100通常是按点赞数、题解数量、被公司出题频率综合排出来的热门题单。它不是官方题库的硬性分类而是社区里无数面试者和面试官用投票和反馈堆出来的“口碑榜”。换句话说这100道题不是谁指定的而是市场筛选出来的能上榜的题目基本都踩中了面试出题的高频区。我对比过几个不同版本的Top100榜单核心题会重复出现比如两数之和、最长回文子串、二叉树的层序遍历、LRU缓存机制、买卖股票的最佳时机、岛屿数量这类题几乎是所有版本都有的。这就说明它们的出题概率高得离谱刷透它们等于直接覆盖了面试考点的主力区。含金量体现在两个层面。第一是直接命中率很多大厂面试官手里就那几个题库Top100里的原题或换皮题出现频率极高刷过和没刷过在面试现场的思考时间可能相差十分钟。第二是间接能力提升这100道题覆盖了哈希表、双指针、滑动窗口、二叉树遍历、递归回溯、动态规划、图的基本搜索、贪心、位运算等核心知识块把它们搞明白遇到新题也有迁移能力。说白了Top100就是一份经过大数据验证的押题清单省去了你自己盲目刷几百道找重点的时间。1.2 适合哪些人群、需要什么基础我见过大一刚学会写循环就跑来刷题的学生也见过工作三年几乎零算法基础的工程师他们都能从Top100里拿到自己想要的东西。如果你符合下面任意一种情况这套题单就适合你准备校招或社招面试目标是一线大厂或中大型互联网公司。已经刷过几十道题但感觉知识点零散想系统梳理高频考点。转码选手算法基础薄弱需要一份“学了就能用”的入门题单。工作中有富余时间想提升代码思维和解题手感。基础方面我不建议零基础直接硬刚Top100。至少需要先掌握一门编程语言的基本语法比如Python里的列表、字典、集合、for循环、while循环、函数定义这些就够了。数据结构不用系统学过刷题过程中现学现卖完全可行我就是一边刷一边补了链表、栈、堆、图这些概念遇到不懂的赶紧看题解评论区效果比啃教科书快得多。但有一点要提醒Top100里有简单题也有不少中等偏难的题比如正则表达式匹配、编辑距离、戳气球这种零基础的人一上来碰到容易心态崩。所以我更推荐的做法是先把Top100里所有“简单”难度的题过一遍建立手感再按分类刷中等题。基础薄弱的不要一上来就追求一天刷五题每天稳定两题、吃透思路比盲目堆数量重要十倍。2. 刷题前的准备工作环境、语言与刷题顺序2.1 环境选择本地IDE还是在线评测我最初是用浏览器直接在LeetCode网页上写的后来刷题量大了发现在线编辑器限制太多——没有自动补全、断点时候不好调试、代码一长眼睛容易花。现在我的主力方案是本地IDE写代码再复制到网页提交。本地IDE我推荐VS Code加Python插件或者JetBrains家的PyCharm也行。写题的时候在本地建一个leetcode文件夹每道题单独一个文件文件名用题号加题目名比如001_two_sum.py。这么干的好处有几点可以自己造测试用例除了题目给的样例还能额外加边界用例。比如数组题里的空数组、全是相同元素、极端大数这些是网上样例覆盖不到的。可以断点调试观察循环中间状态尤其适合排查滑动窗口、双指针这类题。可以留存自己的解题记录以后复习直接翻历史代码比在网页上翻提交记录方便。在线评测也必不可少最终判断代码正确性还是要靠LeetCode的测试集。我的习惯是先在本地写出来跑通样例再贴到网页上提交。如果超时或报错再回本地补测试用例分析。这样既练了本地开发能力又能利用平台判题数据自检。2.2 语言选型Python还是Java我的建议我主力用Python刷题没有特殊偏好的话直接选Python就行。原因很实际题解少、代码短、面试时快。Python写两数之和只要几行一些动态规划、图的题用Python表达比Java简洁一半以上在45分钟面试场景里能省下大把时间用来讲思路和边界情况。但如果你目标明确要投Java后端岗面试时也想用Java手撕算法那还是用Java练手更踏实。刷题语言最好和面试语言一致不然还是会手生。Java刷题也有优势内存要求更严格更容易让你注意到数组越界、内存溢出这类细节LeetCode官方题解很多是Java写的对照起来也方便。我的建议是以Python学算法思维用Java或自己主力语言过一遍高频题保证手感。时间不够就一门语言刷到底别中途反复切换。最忌讳的是因为某道题用Python写不出来就安慰自己“代码风格问题”换个语言重写——那其实是在逃避算法逻辑本身。2.3 按什么顺序刷分类优先还是难度优先我第一次刷Top100的时候是按题号硬刷的结果前面的题还好刷到第10题左右就遇到困难题被卡了两天信心直接掉一半。后来我调整策略按专题分类来刷效率提升非常明显。推荐顺序是先字符串和数组再哈希表接着双指针然后链表再二叉树之后深入递归回溯、动态规划、栈与队列、图最后碰贪心和位运算。这个顺序是从依赖关系出发的数组和哈希表是很多题的数据基础二叉树递归又是动态规划的地基按这个链条学起来不吃力。不建议只按难度刷。简单难度虽然友好但知识点会跳来跳去今天考双指针明天考递归思维没有连续性。分类刷题才能形成“套路记忆”比如连续刷三天滑动窗口再看任何一道滑动窗口题第一反应就是维护一个窗口、记录右指针推进、判断左指针收缩时机这种肌肉记忆只有集中训练才出得来。还有个小细节每刷完一类我就把这一类题的共同套路写在一页笔记里。比如“双指针常见思路对撞指针、快慢指针、滑动窗口”“二叉树题先问自己递归传入什么、返回什么、当前节点要做什么”。事后复习看这一页笔记比重看一遍代码快得多。3. 核心题型拆解Top100里的高频考点与解题套路3.1 数组与双指针从两数之和到滑动窗口数组题是Top100的量大户很多简单题直接考数组操作而中等题通常会用双指针或滑动窗口把数组题拔高一个难度。两数之和是Top100的开山题很多人的LeetCode生涯就是从这里开始的。它的思路也很典型遍历数组用哈希表记录已经见过的值和下标对于当前数target - num只要在哈希表里能找到就直接返回时间O(n)。这里最关键的思维转变是“空间换时间”——用哈希表的O(1)查询换取O(n)的时间复杂度。很多新手想不到这个点是因为总下意识用两层循环暴力解遇到高数据量就超时。滑动窗口是双指针的进阶形态。典型题如无重复字符的最长子串、长度最小的子数组。核心套路就四步右指针不断右移扩大窗口每次移动后更新窗口内的状态比如字符频次、窗口和当窗口不满足条件时左指针右移缩小窗口在合法窗口内记录最优答案窗口内状态用哈希表或数组维护。这里最容易写错的地方是收缩窗口的时机判断。无重复字符的最长子串里收缩条件是窗口内出现了重复字符长度最小的子数组里收缩条件是窗口和大于等于target。不同条件对应不同题的边界规则不能套死。双指针还有一类“对撞指针”典型题是盛最多水的容器、三数之和。三数之和需要先排序然后固定第一个数用双指针查找剩下两个数注意去重逻辑这是最容易漏的地方。3.2 链表操作虚拟头节点的妙用链表题在LeetCode里看着简单提交时却经常因为边界条件翻车比如删除链表节点时处理头节点、判断空链表稍不注意就会空指针异常。我最大的经验是涉及对链表结构进行增删改操作的题统一使用虚拟头节点。虚拟头节点就是new一个临时节点让它的next指向真正的头节点。这样无论操作的是不是原链表的第一个节点都可以用统一的指针移动逻辑最终返回dummy.next作为结果。比如反转链表、两两交换链表中的节点、删除链表的倒数第N个节点用了虚拟头节点后代码逻辑清晰很多。还需要掌握链表遍历、反转、断链拼接这三个基本操作。反转链表有迭代和递归两种写法迭代用prev、cur、next三指针递归要理清“假设剩余部分已经反转好自己该做什么”。我第一次用递归写反转链表时绕了很久后来总结一个口诀递归函数返回的是新链表头当前节点的next的下一个next要指回自己同时把自己的next置空。链表题里还有一个高频点快慢指针。典型题如环形链表、寻找链表中间节点、删除倒数第N个节点。快慢指针的核心在于步速差比如环形链表判断是否有环快指针每次走两步慢指针每次走一步如果存在环快指针一定在某个时刻追上慢指针。3.3 二叉树与递归理解递归就理解了大部分中等题二叉树绝对是Top100的重头戏前中后序遍历、层序遍历、验证二叉搜索树、最近公共祖先、二叉树的最大深度、路径总和、树的子结构等等直接占了好几十道。二叉树题的解题框架非常固定几乎所有题都能归结到三种遍历方式的变体。我自己刷到后面总结了一套递归模板def traverse(root): if not root: return # 前序位置写逻辑刚进入节点 # 中序位置写逻辑左子树返回后 # 后序位置写逻辑右子树返回后 traverse(root.left) traverse(root.right)不同题只在于逻辑代码放在哪个位置。比如求最大深度应该用后序逻辑先算左右子树深度再取最大值加一比如验证二叉搜索树要用中序遍历检查是否严格递增。把模板用熟新题只考虑“在哪一步做什么”就行了。递归的难点在于人脑很难模拟完整调用栈越陷越深会晕。我的经验是不要把每一层展开看而是假设递归函数本身已经正确只考虑当前层该做什么、返回值是什么。这个“递归信任”思维是解所有二叉树题的关键。做题时先在纸上明确三点递归参数要不要变、终止条件是什么、返回值代表什么然后大胆写。层序遍历是二叉树题里另一个重点用队列宽度优先搜索。框架也很固定while队列非空先取当前层的节点数循环取出本层节点同时把下一层节点入队。这样能获得每一层的节点列表处理按层统计、层内反转、每层最大值等变体题。3.4 动态规划状态定义才是灵魂动态规划是Top100里最容易让人头疼的题型但也是面试中区分度最高的考点。典型题有爬楼梯、打家劫舍、最长递增子序列、编辑距离、最大子数组和、完全平方数、戳气球等等。我的心得是动态规划题不要先看状态转移方程先想清楚两个问题这个问题的子问题怎么定义dp数组或状态下标和值分别代表什么。比如爬楼梯dp[i]表示爬到第i阶有几种方法转移方程是dp[i] dp[i-1] dp[i-2]因为最后一步要么跨一阶要么跨两阶。有了状态定义转移方程往往是自然推导出来的——“当前状态可以由哪些更小的状态得到”。编辑距离是动态规划里的“劝退题”之一。它的状态定义是dp[i][j]表示word1前i个字符转换成word2前j个字符需要的最少操作数。转移时考虑当前字符是否相等相等不用操作不相等就取插入、删除、替换三种操作的最小值再加一。这个题我建议把矩阵亲手填一遍填完就懂了。动态规划的另一类分支是“自顶向下的记忆化搜索”本质是递归加缓存。比如打家劫舍、单词拆分用字典或数组记录已计算过的子状态避免重复计算。面试时如果状态定义想得很吃力先从记忆化搜索下手写出递归框架再用dp表优化是更稳妥的路子。做题多了会发现动态规划题型的“识别特征”求最值、方案数、可行性判断并且问题能拆解成重叠子结构。一看到这种特征就条件反射往动态规划上想比自己瞎琢磨强得多。3.5 栈与单调栈括号问题与基本计算器栈的题表面简单但Top100里的栈题往往不止考“栈”本身还会结合字符串处理、表达式解析。典型题有有效的括号、最小栈、每日温度、柱状图中最大的矩形、基本计算器等。有效的括号是栈入门题思路是遇到左括号入栈遇到右括号就弹栈对比匹配。进阶的变体是带通配符的括号匹配、括号生成、删除无效括号。刷这类题的关键是记住栈只存“暂时没被匹配的东西”配对成功后及时弹出。单调栈是栈的高阶用法思路是维护栈内元素的单调性常用于数组题里求左边或右边第一个更大或更小的元素。典型题每日温度就是找到每个元素右侧第一个比它大的元素的距离。做法是遍历数组维护一个存下标的栈栈底到栈顶对应元素值递减。当前元素比栈顶元素对应值大时不断出栈并计算答案。这个技巧初看很绕但练两三道题就顺手了我建议手动模拟一遍入栈出栈过程比看十遍讲解都有用。基本计算器是栈题里的经典难题很多搜索热词里都在讨论它。核心套路是使用栈保存数字和符号状态处理括号时把状态压栈遇到右括号再弹栈恢复。后面第4节我会拿这道题完整走一遍流程。4. 实操记录以“腐烂的橘子”和“基本计算器”为例走一遍完整流程4.1 腐烂的橘子多源BFS的经典套路这道题在搜索热词里出现频率很高题目编号是994LeetCode原题叫“腐烂的橘子”。题目说一个网格里0表示空1表示新鲜橘子2表示腐烂橘子每分钟腐烂橘子会向上下左右四个方向传播问多少分钟后所有新鲜橘子都腐烂如果不可能全部腐烂则返回-1。看到“扩散”“传播”这类关键词第一反应就是BFS广度优先搜索而不是DFS。因为每分钟同步向外扩散一格天然就是BFS层数的问题。这道题和“岛屿数量”的区别在于BFS的起点有多个所有初始的腐烂橘子都是起点所以要用多源BFS。解题步骤拆开看就四步初始化队列把网格中所有腐烂橘子的位置入队同时统计新鲜橘子数量。如果一开始就没有新鲜橘子直接返回0。用队列循环每一层代表一分钟。每次处理当前层的所有腐烂橘子向四周传播把新腐烂的橘子入队同时新鲜橘子数量减一。BFS结束后如果新鲜橘子数量不为0返回-1否则返回层数。代码可以这样写def orangesRotting(grid): m, n len(grid), len(grid[0]) from collections import deque queue deque() fresh 0 for i in range(m): for j in range(n): if grid[i][j] 2: queue.append((i, j)) elif grid[i][j] 1: fresh 1 if fresh 0: return 0 minutes 0 directions [(1,0),(-1,0),(0,1),(0,-1)] while queue: size len(queue) minutes 1 for _ in range(size): i, j queue.popleft() for di, dj in directions: ni, nj i di, j dj if 0 ni m and 0 nj n and grid[ni][nj] 1: grid[ni][nj] 2 fresh - 1 queue.append((ni, nj)) if fresh 0: return minutes return -1 if fresh 0 else minutes注意几个容易栽的细节第一BFS层数怎么计数我习惯在每层开头先取出当前队列长度然后一次性处理完本层再让分钟数加一。如果把minutes的累加放在格子循环里就会把每分钟扩展一层算成每个格子一分钟。第二遍历完成后如果还有新鲜橘子要返回-1。第三网格大的时候要注意方向数组不要写错符号。这道题练完以后多源BFS就掌握了。面试遇到“岛屿数量”“墙与门”“矩阵中的最短路径”这类题可以把同样思路迁移过去把所有符合条件的起点一次性入队再逐层传播。4.2 基本计算器处理括号与运算符优先级“基本计算器”在LeetCode上有224、227、772等好几个版本常见要求是实现一个能处理加减乘除和括号的表达式求值。Word版224要求支持加减和括号227要求支持加减乘除更变态的扩展题会要求处理乘方、负数等情况。Top100讨论度极高因为它最考“状态管理”的能力。拿224来说表达式里只有-和括号没有乘除。这种题不需要真正构建语法树用栈就能解决。核心思路是当遇到数字时累加解析出完整数字遇到或-时把当前数字乘以符号位1或-1加入结果遇到左括号时把当前结果和符号位压栈然后重置结果和符号继续处理括号内部遇到右括号时从栈里弹出之前的符号和结果和括号内计算结果合并。代码拆解如下def calculate(s): stack [] num 0 sign 1 res 0 for ch in s: if ch.isdigit(): num num * 10 int(ch) elif 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() # 弹出括号前的结果 if num ! 0: res sign * num return res字符串里可能有空格所以判断用ch.isdigit()跳过空格。我调试的时候发现最坑的是“括号前有负号”的情况比如(1-(23))遇到第二个左括号时当前sign是负号要把符号压栈否则弹栈时符号就错乱了。这个细节必须自己走一遍模拟才能体会。如果是227这种带乘除的版本就不能再用累加res的简单方式了得改成用栈保存每个带符号的数字遇到乘除时从栈中弹出栈顶元素与当前数字做运算再压回栈。套路的本质是延迟计算把加减法留到最后统一做。这两道题刷透后任何“实现一个简单计算器”类的题基本都拿得下。面试官如果想深挖还会在此基础上问“如果加括号呢”“如果支持变量呢”变体思路是一样的。4.3 复盘这两道题教会我们什么拿“腐烂的橘子”和“基本计算器”这两道题当例子是因为它们分别代表了图搜索和状态栈两个大类而这两类在Top100里反复出现。腐烂的橘子让我重新认识了BFSBFS不仅用于找最短路径任何“按时间/层数同步扩散”的描述都该想到它。多源BFS则解决了一个准入门槛问题——不是只有一个起点而是多个起点起点数量不影响框架只需要初始化时把所有起点入队。面试时遇到“从多个初始点同时开始扩散”的题直接套这个模板就行。基本计算器让我反思了一个问题表达式的状态管理其实等价于“现场保护”。遇到左括号就是“现场暂停并保存”遇到右括号就是“恢复现场并计算”。很多递归和栈的题背后都是这个思想。掌握了这种思维像“实现MIN函数栈”“有效的括号”“逆波兰表达式求值”都成了同一类问题的变体。复盘不只是看完答案就过我的方法是在AC之后马上再在空白纸上诉说一遍思路并且在第二天不借助任何提示重新实现一遍。能白板默写出来才算真正学会。5. 刷题过程中的常见问题与排查技巧实录5.1 提交超时先检查是不是死循环刷Top100的过程中我遇到最多的问题就是“Time Limit Exceeded”。第一次遇到时我很慌后来发现大多数超时不是算法复杂度的问题而是死循环。死循环最常见的触发点while循环里忘了移动指针或者循环条件永远不满足退出条件。比如链表题里反转链表时如果“断链”后没有把指针指到正确位置就会在链表里来回绕。滑动窗口里右指针忘记右移窗口永远不扩大也会死循环。排查方法是在本地IDE里给while循环加上一个计数器超过一定次数就打印输出。我一般在写while循环时会确认三件事循环变量是否在每次迭代中更新退出条件是否可能永远为假边界case比如空输入是否会让循环直接崩掉。排除死循环后再考虑局部优化。比如Python里用dict替代列表查找用集合去重多用局部变量减少全局查找。但不要一上来就做微优化先确认算法复杂度本身是对的比如O(n^2)的暴力解在数据量10^5时就是过不了这时优化局部代码没用得换算法。5.2 内存爆了数组越界与递归栈溢出“Memory Limit Exceeded”或“RecursionError”是另一类高频问题。数组越界常见于二维网格题比如上下左右遍历时检查坐标是否在合法范围很多人只判断了边界但漏了坐标可能为负数的情况。我的习惯是写一个is_valid函数统一判断坐标合法性代码里所有方向遍历都走这个函数避免漏判。递归栈溢出最常见的是二叉树退化成链表的情况。比如二叉树的最大深度如果树本身是一条长链递归深度可能达到10000多层Python默认递归深度约1000直接报错。遇到这类题要考虑用迭代写法比如显式用栈模拟递归或者用层序遍历。刷题时还要注意动态规划里递归记忆化搜索如果递归状态空间很深也可能触发递归限制这时就要转成迭代的dp数组写法。还有一个不太容易发现的坑用sys.setrecursionlimit硬调递归深度上限确实能让测试跑过但面试时并不加分反而暴露了不懂递归栈风险。更好的做法是用迭代方案或在设计递归时就保证深度可控。5.3 总是想不出解法建立自己的套路库刷题第20天左右会进入一个瓶颈期新题看起来陌生题解看得懂自己写就卡壳。这个阶段最有效的做法不是硬刷而是建套路库。所谓套路库就是把Top100里遇到的高频思想整理成模板。比如“复杂度要求O(log n)的查找题就往二分查找上想”“求最短步数的图论题BFS永远优先考虑”“子数组或者子串最值优先想滑动窗口和前缀和”“字符串子序列相关多半是动态规划”。每刷一道题就在题目标记对应的套路标签比如双指针、单调栈、BFS、回溯、拓扑排序、贪心、区间合并等。一段时间后你再看到新题就会像检索一样快速定位到几个可能的套路方向。我自己还在本子上画了一张“题型-方法对照表”比如连续子数组和 → 前缀和 哈希表求第K大 → 堆或快速选择无向图连通分量 → 并查集或DFS最小编辑距离 → 二维DP括号匹配 → 栈这个表越用越顺手因为它逼着我把每道题放进框架而不是孤立记忆。面试时即便遇到没见过的题也能顺着几个方向试探不至于大脑空白。5.4 刷了忘忘了刷记忆曲线与复习策略遗忘是刷题人最大的敌人。我刷到第80题时回头看第10题的代码已经完全陌生这太正常了。对抗遗忘靠的不是意志力而是间隔重复。我的具体策略是“三遍法”。第一遍独立做二十分钟没思路就看题解看懂后在本地自己敲一遍写注释梳理思路。三天后不看任何资料重新做这道题如果还能独立AC说明真懂了。两周后再在题单里随机抽查一批旧题重点复习当时做得费力、靠题解才过的题。这样一轮下来记忆留存率明显提高。另一个实用技巧是针对Easy题快速过中等题记套路Hard题不硬啃。Top100里有几道Hard题比如正则表达式匹配、戳气球我第一次做确实崩溃。后来发现这类题在面试中出现的概率远低于中等题优先掌握基础套路更重要。先把简单和中等题刷到“看到题能快速反射出解法类型”的程度再回头攻克Hard心态就稳多了。还有一点刷题期间要控制连续作战的疲劳。我分享一个“番茄刷题法”每25分钟专注刷题休息5分钟每天总共两个番茄钟就够了。人每天的高强度脑力资源有限用完了强行刷只会机械抄答案效率很低。长期来看持续稳定的刷题节奏比突击一整天强太多。我个人这几年最深的体会是算法题刷的不是题是思维模型。Top100的最大价值在于用最小成本把最常见的思维模型集中过一遍。等你真正把这100道题嚼碎了回头再看任何一道中等难度的新题很少会再有无从下手的感觉。刷题这条路没有什么捷径但不代表一定要绕远路认准Top100一题一题走着慢慢就会发现土路被踩成了高速。