十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

力扣刷题全攻略:八大题型分类与Hot 100高效刷题路线

力扣刷题全攻略:八大题型分类与Hot 100高效刷题路线 我在知乎和博客后台看到最多的一条私信就是力扣LeetCode到底该怎么刷为什么我刷了几百题面试碰到新题还是懵说实话这不是个例。我见过太多人打开力扣之后第一反应就是按题号从第 1 题开始一路往后刷刷到 30 题左右热情耗尽刷到 100 题开始怀疑人生。问题不在努力程度而在大部分人手里缺一张题型地图。力扣目前有 3000 多道题如果按题号硬刷你只是在跟题库比耐力而不是在训练算法思维。这篇内容我就结合自己的刷题经验把力扣题型分类、各类题型的核心解法和热题 100Hot 100的正确打开方式完整梳理一遍顺便把三维接雨水这种终局型题目拆开给你看。不管你是准备校招、社招还是单纯想补算法短板这篇都值得存下来反复看。1. 先把力扣的题型地图摊开我为什么把题目分成八大类很多人对题型分类有误解以为分类就是按数据结构贴标签数组题、链表题、二叉树题。这种分法太表面。同样是数组题两数之和考的是哈希映射下一个排列考的是找规律接雨水考的是单调栈合并区间考的是排序加扫描。如果只按数据结构分类你拿到一道新题还是会懵。我自己的分类标准是按解题思维来分也就是看到题目的第一反应应该往哪个方向去靠。下面这张表是我自己整理的核心题型分类基本覆盖了力扣热题 100 和面试中出现频率超过 90% 的题目类型题型分类核心解题思维典型代表题面试出现频率数组与哈希空间换时间哈希表配对、计数、去重两数之和、字母异位词分组、最长连续序列极高链表指针移动、画图模拟、快慢指针反转链表、环形链表、LRU缓存高双指针与滑动窗口区间维护、单调性利用三数之和、无重复字符的最长子串极高栈与单调栈最近更大/更小元素、括号匹配有效的括号、每日温度、柱状图中最大矩形高二叉树与递归递归遍历框架、分治思想二叉树层序遍历、最近公共祖先极高图论与搜索DFS、BFS、拓扑排序岛屿数量、课程表、腐烂的橘子中高回溯算法决策树穷举、剪枝全排列、子集、括号生成高动态规划状态定义、状态转移、最优子结构爬楼梯、最长递增子序列、编辑距离极高贪心/堆/二分局部最优、优先级队列、有序性二分跳跃游戏、前 K 个高频元素、搜索旋转排序数组高为什么我坚持按思维分类因为刷题的本质是建立条件反射。看到一个题目特征你脑子里能立刻弹出对应的套路这才是刷题的意义。比如题目里出现连续子数组子串窗口这类词优先想到滑动窗口题目里出现最近更大/更小柱状图温度这类词优先想到单调栈题目里出现所有可能组合排列基本就是回溯题目里出现最大/最小/方案数/最长大概率是动态规划。简单题考基础中等题考套路困难题考组合。你注意观察力扣的题目难度分布就会发现困难题很少考一个孤立的知识点几乎都是把 2 到 3 个题型粘在一起。最典型的就是三维接雨水它同时考了堆、BFS 和木桶效应我后面单独用一章拆它。先把分类地图装进脑子再开始刷题效率完全不一样。2. 线性表类题型数组、链表、哈希、双指针的解题套路2.1 数组与哈希几乎所有题的起点数组是力扣里最庞大的一个家族。为什么数组题最多因为它是最基础的数据结构几乎所有算法都要建立在数组操作上。数组类题目的核心套路就几个遍历、排序、哈希映射、原地修改。排序是数组题最重要的辅助手段很多题一看数组是无序的第一步先排序思路瞬间就打开了。比如合并区间这题先按区间起点排序再顺序扫描合并逻辑极其清晰。哈希表的本质是空间换时间。最经典的例子就是两数之和暴力解法是两层循环时间复杂度 O(n²)。用哈希表遍历一次数组每遇到一个数就查一下 target - 当前数 在不在哈希表里时间复杂度直接降到 O(n)。def two_sum(nums, target): seen {} for i, num in enumerate(nums): complement target - num if complement in seen: return [seen[complement], i] seen[num] i return []我刷了这么多年题总结出一个判断依据只要题目里出现找配对查重复统计频次这类需求先想哈希。不要一上来就暴力。很多简单题暴力能过但面试时暴力解基本等于送命题面试官一定会追问能不能优化哈希就是最常见的优化方向。2.2 链表类画图比空想重要链表题在力扣里的数量不算最多但面试出现率很高因为链表能同时考察指针操作和边界处理能力。链表题的难点从来不是算法思路复杂而是指针指来指去容易绕晕。我个人强烈建议做链表题先在纸上画图把每一步的指针变化画出来再把图转成代码。举个例子反转链表这题看起来简单但很多人写代码时总在 next 指针上翻车。用三指针法prev 指向当前节点的前一个节点curr 指向当前节点next 暂存下一个节点每次循环把 curr.next 指向 prev然后整体右移def reverse_list(head): prev None curr head while curr: next_node curr.next curr.next prev prev curr curr next_node return prev链表题的几个常见变体我都列一下快慢指针找链表中点、判断是否有环环形链表、链表中倒数第 k 个节点递归思路反转、两两交换链表中的节点链表排序比如合并 K 个升序链表LRU 缓存这是链表 哈希表的经典组合。说到 LRU 缓存我建议每个刷题的人都认真做一遍。它表面是设计题实际考察的是你对哈希表和双向链表底层机制的理解。哈希表用来快速定位节点双向链表用来维护访问顺序。这道题能独立做出来你对链表这类的理解就基本过关了。2.3 双指针与滑动窗口怎么判断该用哪种双指针是力扣里最实用、最好用的一类技巧但它其实包含三种完全不同的模式很多人混为一谈相向双指针左右指针从两端向中间移动典型应用是三数之和盛最多水的容器同向双指针一个快一个慢或者一个先走一个后走典型应用是无重复字符的最长子串最小覆盖子串快慢指针最典型的是链表里判断是否有环。相向双指针的核心前提是有序性。如果数组本身有序或者经过排序后变得有序那么左右指针可以根据当前和的大小决定移动哪一边避免暴力枚举。你还记得三数之和吗固定一个数剩下两个数用相向指针扫时间复杂度从 O(n³) 降到 O(n²)。滑动窗口是同向双指针的升级版专门解决连续子数组/子串的最优解问题。它的逻辑其实很简单右指针不断向右扩展窗口当窗口内不满足条件时左指针向右收缩直到重新满足。这个过程中不断记录窗口长度或窗口内容就能找到最优解。我见过很多人死记滑动窗口模板却不知道为什么要收缩左边界。原因很简单窗口类似于一个可伸缩的区间右指针负责扩展左指针负责去重/去除无效部分。只有两个指针都动起来才能保证每个元素最多被访问两次时间复杂度才是 O(n)。没有这个理解遇到最小覆盖子串这类复杂滑动窗口题就会翻车。3. 树与图递归扎实了算法就通了一半3.1 二叉树与递归的关系递归是算法面试里绕不过去的坎而二叉树正是学习递归的最好载体。为什么因为二叉树本身就是递归定义的一棵二叉树由根节点、左子树、右子树组成而左子树和右子树又分别是二叉树。这种天然的自相似结构决定了树的题目绝大多数都能用递归优雅地解决。很多初学者学递归时总想着把每一步递归调用都掰开看结果越看越晕。我的建议是换个思路不要关注递归的每一层调用细节你只需要相信函数能处理规模更小的子问题然后确保当前层的逻辑正确。这种相信子问题的思维就是递归的核心。做二叉树题时你只需要问自己三个问题当前节点需要做什么左子树交给递归函数处理它应该返回什么右子树同理它们的结果如何汇总到当前层拿二叉树的最大深度举例递推关系非常清晰当前节点的最大深度 左右子树最大深度的较大值 1。这就是典型的分而治之。3.2 二叉树的遍历框架与经典题变体二叉树的题目变化再多根基都是三种遍历顺序前序遍历、中序遍历、后序遍历。所谓前中后指的是根节点在什么时候被访问。前序是根左右中序是左根右后序是左右根。为什么中序这么特殊因为二叉搜索树BST的中序遍历结果是递增序列。看到验证二叉搜索树这题很多人的第一反应是递归判断每个节点是否在 (min, max) 区间内其实也可以用中序遍历检查结果是否严格递增。两条路径都能解但理解中序性质会给你多一把钥匙。层序遍历也很常考。它不依赖递归而是依赖队列先把根节点入队每次弹出当前节点时把左右孩子入队一轮下来就是一层。注意如果需要按层输出需要先记录当前队列大小再循环处理这一层否则队列长度会混合多层节点。二叉树的最近公共祖先LCA是另一道高频题。它的递归思路很巧妙如果当前节点是空或者等于 p 或 q直接返回当前节点否则递归处理左子树和右子树。如果左右返回值都不为空说明 p 和 q 分别在两侧当前节点就是最近公共祖先如果只有一侧不为空说明 p 和 q 都在这侧。3.3 图的遍历BFS 与 DFS 的适用边界树是一种特殊的图无环连通图所以图的遍历和树的遍历在思路上是相通的只是图多了一个关键步骤标记已访问。树天然有层级关系不会死循环图必须有 visited 集合不然就会无限循环。DFS 内部依赖递归或者显式栈适合解决路径是否存在连通块大小岛屿数量这类问题。比如经典题岛屿数量遍历整个网格遇到陆地就把整块岛屿通过 DFS 全部标记为已访问同时计数器加一。写起来很爽。BFS 依赖队列适合解决最短路径最小步数扩散层数这类问题。比如腐烂的橘子把所有腐烂橘子同时加入队列记录扩散了几层就是全部腐烂需要的时间。BFS 的层数天然对应最短距离这是 DFS 达不到的效果。图论里还有一个高频考点是拓扑排序代表题是课程表。它的本质是判断有向图是否存在环。核心是 Kahn 算法统计每个节点的入度把入度为 0 的节点加入队列不断弹出并减少相邻节点入度。最后如果处理的节点数不等于总节点数说明图里有环。4. 动态规划与回溯最需要设计感的两类题4.1 回溯就是一棵多叉树的深度优先遍历回溯算法在很多初学者眼里是玄学但它本质上就是一棵多叉树的 DFS。每当你遇到所有可能的组合所有排列选择路径这类问题都要考虑回溯。回溯模板其实非常固定我把它拆成三部分def backtrack(路径, 选择列表): if 满足结束条件: 记录结果 return for 选择 in 选择列表: 做选择 backtrack(路径, 选择列表) 撤销选择全排列就是最典型的练习。每次从选择列表里挑一个数加入路径然后递归处理剩余数字最后撤销选择。画成树形结构你立刻就能看到路径和选择列表在每一层的状态变化。回溯题目的差异主要在剪枝。比如组合总和要求候选数字总和等于目标值那么当当前路径总和已经超过目标值时就没必要继续递归了直接 return 就是剪枝。剪枝写得好性能从 O(2^n) 到 O(n!) 不等能大幅减少无效分支。子集、全排列、组合总和、括号生成这几道题我建议放到一起刷。你会发现它们共用同一个模板只是结束条件和剪枝逻辑略有差别。练熟了之后N 皇后这类困难题也会变得有迹可循。4.2 动态规划五步走动态规划是整个力扣题库里最劝退的部分没有之一。它考察的不是编码能力而是问题建模能力。我的经验是所有 DP 题都可以用一套五步法来拆解先把这五步写在纸上再动笔写代码。第一步明确 dp 数组的含义。这是最重要的一步dp[i] 到底代表什么必须想清楚。以爬楼梯为例dp[i] 表示爬到第 i 阶有多少种方法。含义一旦定错后面全错。第二步找递推公式。爬楼梯的递推公式是 dp[i] dp[i-1] dp[i-2]因为到达第 i 阶只能从第 i-1 阶迈一步或者从第 i-2 阶迈两步。第三步初始化。dp[0] 和 dp[1] 要单独处理。爬楼梯问题里 dp[0] 1dp[1] 1或者也可以认为 dp[0] 1dp[1] 2不同定义对应不同的写法和下标初始化要跟 dp 定义保持一致。第四步确定遍历顺序。大部分一维 DP 是从前往后遍历但最长递增子序列这类题需要两层循环外层遍历每个位置内层遍历前面的所有位置。遍历顺序错了递推公式再对也白搭。第五步举例验证。用一个小例子手动推导一遍确认 dp 数组每个值都符合预期再提交代码。经验之谈这一步能帮你避免大量 WA。怎么判断一道题是 DP 题我的判断依据是三个特征同时存在求最优解或方案数存在某种选择或决策子问题高度重叠。打家劫舍就是典型每个房子都有偷和不偷两种选择当前最优解依赖前两个房子的状态。4.3 背包问题与 DP 分支DP 题里的一个大家族是背包问题。0-1 背包和完全背包经常出现在中高难度题里比如分割等和子集零钱兑换。0-1 背包的核心思想是每个物品选或不选。二维 DP 转一维 DP 有一个关键细节内层循环必须倒序遍历这样才能保证每个物品只被选一次。如果是完全背包每个物品可以选无限次内层循环要正序遍历因为正序允许同一个物品被反复更新。这里我想强调一句硬背内层倒序还是正序没用你得理解为什么。一维数组滚动更新的本质是复用上一层的结果倒序遍历时更新 dp[j] 用的是还没被当前物品污染过的旧值所以能保证只选一次。想通这一点背包问题就不会再错了。DP 的其他重要分支包括区间 DP最长回文子串、状态 DP买卖股票的最佳时机用二维数组表示持有/不持有、字符串 DP编辑距离、最长公共子序列。编辑距离是经典的困难题它的 dp[i][j] 表示 word1 前 i 个字符转换成 word2 前 j 个字符需要的最少操作数递推公式需要考虑增、删、改三种操作。这道题能独立写出来你的 DP 基本功就过关了。5. 从二维接雨水到三维接雨水一道题如何串起多个题型分类5.1 二维接雨水的三类解法接雨水在力扣里已经是图腾级别的题目了热题 100 里有它面试高频题里有它。二维接雨水LeetCode 42的常见解法有三条路恰好对应了不同题型的思维暴力解法的思路是按列求对于每一列分别找到左右两侧的最大高度当前列能接的水量等于 min(leftMax, rightMax) - height[i]累加即可。时间复杂度 O(n²)能过但不够好。双指针解法把时间复杂度降到 O(n)核心是理解每个位置的水量由左右两侧最大值的较小值决定。左指针从左往右右指针从右往左哪边最大值更小就先处理哪边因为该侧的水量上限已经确定。单调栈解法是另一种思路按行求。栈内维护递减的高度遇到比栈顶高的柱子时栈顶元素就是低洼处它左右两边都有更高的柱子挡着于是能形成一个完整的水槽。每次弹栈时计算的是这一层的水量。def trap(height): stack [] ans 0 for i, h in enumerate(height): while stack and h height[stack[-1]]: top stack.pop() if not stack: break left stack[-1] width i - left - 1 bounded_height min(height[left], h) - height[top] ans width * bounded_height stack.append(i) return ans我强烈建议三道解法都自己写一遍。因为面试官特别喜欢追问有没有更好的做法能说出三种解法和各自的复杂度这道题基本就满分了。5.2 三维接雨水的核心从边界向内收缩三维接雨水LeetCode 407Trapping Rain Water II可以说是一道综合题它把二维接雨水里边界决定水量的思想从两边拓展到了整个外圈。我第一次做这题时也卡了很久后来理解到它是堆 BFS 的经典组合才觉得豁然开朗。二维接雨水是从左右两端往中间收因为两端是二维世界的边界。三维接雨水的边界是地图最外圈的一整圈格子。核心思想用一句话概括每次从边界中选出高度最低的格子向内部扩散内部格子能接的水量由这个最低边界决定。为什么一定是最低边界因为盛水的上限遵循木桶效应一个位置能不能存住水取决于四周挡板中最低的那一块。如果从外圈最低的格子开始向内收缩就能保证用当前已知的最低边界去判断内部格子是否能存水。具体步骤拆开是这样定义一个二维 visited 数组初始都未访问把矩阵最外圈的所有格子加入一个小顶堆同时标记为已访问从堆中弹出高度最小的格子 top遍历 top 的上下左右四个邻居如果邻居未访问计算它是否能接水——邻居高度低于 top 高度时接水量 top 高度 - 邻居高度然后把邻居的高度更新为 top 高度再入堆因为此时水已经把邻居垫高了后续要以这个垫高后的高度作为新边界如果邻居本来比 top 高就直接以原高度入堆重复 3 和 4直到堆为空。import heapq def trap_rain_water(height_map): if not height_map or not height_map[0]: return 0 m, n len(height_map), len(height_map[0]) visited [[False] * n for _ in range(m)] heap [] for i in range(m): for j in range(n): if i in (0, m-1) or j in (0, n-1): heapq.heappush(heap, (height_map[i][j], i, j)) visited[i][j] True water 0 directions [(0,1),(0,-1),(1,0),(-1,0)] while heap: h, x, y heapq.heappop(heap) for dx, dy in directions: nx, ny x dx, y dy if 0 nx m and 0 ny n and not visited[nx][ny]: visited[nx][ny] True if height_map[nx][ny] h: water h - height_map[nx][ny] heapq.heappush(heap, (max(h, height_map[nx][ny]), nx, ny)) return water这个算法的时间复杂度是 O(M * N * log(MN))因为每个格子最多入堆一次堆操作的时间复杂度是对数级别。第一次接触这题你不需要追求一次写对重点是理解用堆维护动态边界这个思维模型。在算法面试里这种思维模型比具体的题目更有迁移价值。5.3 这题透露的进阶信号很多人做完二维接雨水就以为自己把接雨水解决了但实际上三维版本才是真正的分水岭。它看起来只是多加了一个维度但解法完全不同二维用双指针或单调栈三维必须上优先队列。这个变化告诉我们一个规律当题目从一维变二维、从线性变平面解题工具往往会从双指针/栈升级为堆/BFS。类似的升级还有组合总和回溯到二维费用背包DP合并两个有序链表双指针到合并 K 个有序链表堆爬楼梯一维 DP到不同路径 II二维 DP。所以刷题时要有意识地去对比同一类题目的一维版本和二维版本。三维接雨水就是很好的一道进阶题它把堆、BFS、边界条件、木桶效应四个知识点全部揉在一起一道题练到位等于同时复习了四个分类。6. 力扣 Hot 100 刷题顺序安排热题 100 如何配合题型分类使用6.1 热题 100 的组成逻辑力扣的热题 100Hot 100是很多人刷题的起点但它并不是按教学逻辑排版的而是按热度排序。所以直接按列表顺序刷并不是最优策略。Hot 100 的题型分布大致是数组与哈希约占 25 题二叉树约占 15 题动态规划约占 15 题链表约占 10 题回溯和滑动窗口各占 8 题左右其余被栈、堆、图、贪心等瓜分。这个分布其实反映了面试命题的真实偏好数组/哈希和二叉树是最基础、最高频的考题来源动态规划是区分中等生和优等生的分水岭。所以我的建议是把 Hot 100 当成习题库而不是当成目录。6.2 我的推荐刷题顺序四轮刷法我不太建议新手按 Hot 100 原顺序刷更不建议按题号刷。我自己带过几个学弟学妹一直用的都是四轮刷法效果比较稳。第一轮线性表打底。把数组与哈希、链表、双指针与滑动窗口的题目集中刷掉大概 30 到 35 题。这轮目的是建立最基本的编码手感熟悉哈希、双指针这些基础工具。第二轮树、图与回溯。这轮也差不多 25 到 30 题。重点练递归思维把二叉树的遍历、LCA、DFS/BFS、回溯模板全部过一遍。学完这一轮你会发现自己看很多中等题都不怵了。第三轮动态规划和贪心。这一轮题量不用太大但每道题都要精做。建议从爬楼梯、打家劫舍、最长递增子序列、编辑距离、零钱兑换这几个经典题开始再逐步过渡到买卖股票系列、背包问题。第四轮综合刷题。把 Hot 100 剩下没做的题扫一遍遇到不会的先独立思考 20 分钟再看题解重点记录题解里出现的组合型思路。三维接雨水就适合放在这一轮做。点头这个顺序是因为每一轮都在为下一轮打基础。线性表不过关做树题时操作数组就不会顺手递归不过关动态规划的递推公式也很难写顺。按这个顺序刷比从题库里随机选题效率高很多。6.3 刷题节奏与复盘方法刷题最怕的是一天刷 10 道题一周后全忘光。我的经验是宁可一天只刷 2 道新题也要留出时间做旧题的复盘。我自己有一个很笨但有效的复盘方法给每道做过的题建一个索引记录四个信息——题型分类、核心思路、复杂度、易错点。这样刷完 100 题后我不需要重做每一道题只需要快速浏览索引重点看那些易错点标记多的题重新在纸上写一遍核心代码。人的记忆曲线很残酷第一天刷的题第三天你就会忘记大半。所以新题和旧题的比例我一般控制在 1:1。今天刷了 2 道新题就必须重做 2 道三天前做过的题。这个习惯坚持一个月效果会明显好过每天猛推新题。7. 容易踩的坑和我现在坚持的刷题习惯7.1 四个最常见的坑第一个坑是只刷简单题。简单题能带来满足感但面试考察的核心集中在中等题困难题反而没那么高频。我见过有人 Hot 100 刷了两遍却连最长递增子序列都写不利索就是因为只挑简单题刷。简单题只能热身不能当主菜。第二个坑是边看答案边刷。有些人做题五分钟没思路就打开题解看完觉得自己会了合上题解又写不出来。这是典型的眼睛会了手不会。我的建议是独立思考至少半小时哪怕写不出完整代码也要把思路卡在哪里、为什么卡住写下来再看题解。这样题解的价值才会真正内化。第三个坑是死磕难题。一道题想了一下午还没头绪其实边际收益已经很低了。我的原则是面试中一道题通常只给 30 到 45 分钟所以平时练习也按这个标准来。超时就标记为待重点攻克先看题解过一段时间再重新独立写一遍。第四个坑是只写不总结。刷题如果不记录思路刷完 50 题后你会发现脑子还是空的。每个专题刷完后都应该用自己的话写一小段总结。哪怕只是几十个字也能强迫大脑把零散的知识点串成体系。7.2 我现在怎么安排刷题我现在的工作节奏下没办法像学生时代那样每天泡在题库里但我也摸索出一套可持续的计划。我的时间单位是专题周。每周只聚焦一个题型分类比如这周刷滑动窗口下周刷动态规划再下周刷图论。每天固定上午 45 分钟刷一道新题晚上 15 分钟复盘旧题。周末选一道困难题做深度拆解把思路写成笔记。这样一周下来一个专题大概能积累 10 道左右的新题配合复盘记忆留存率明显比乱刷高。做笔记也很有讲究。我习惯用题型 题目 解法 复杂度的模板但笔记里最重要的部分是为什么我想不到这个解法。很多题看完答案很简单难的是下次遇到类似的题还能不能想到。所以我每次看完题解后都会问自己题干里哪个特征可以作为下次判断的依据把它写进笔记下次遇到类似题先扫一眼特征。7.3 最后说点实在话刷力扣这件事说实话没有捷径。题型分类、刷题顺序、复盘方法这些都是手段真正起作用的还是每天稳定的投入。我自己刷到现在的体会是面试官真正考察的不是你背了多少道题而是你把多少道题内化成了自己的思维方式。题型分类能帮你更快地识别题目模式但前提是你得真的动手写够一定量的题。热题 100 和 Hot 100 是很好的题单但比起刷完它更重要的是刷完之后你能清楚地说出每一类题型的核心思路、适用条件和复杂度上限。再分享一个小技巧每当我学完一个新题型我会尝试用这个题型的思维去重做三道以前做过的旧题。比如学了单调栈就回去看看最大矩形和每日温度能不能用新思路再做一遍。这种旧题新做的训练比不断刷新题更能加深对题型的理解。希望这套力扣题型分类和刷题路径能让你少走一些我当年走过的弯路。
返回列表