刷力扣刷到三百多题的时候,我发现自己陷入了一个很尴尬的状态:题做了不少,但回头想找一道之前写过的解法,翻半天翻不到,或者找到了当时的代码,却完全想不起来当时为什么这么写。后来我花了两周时间,把做过的题和收藏的题解重新过了一遍,从零开始整理了一份“力扣优秀题解汇总”。这份汇总不是简单收藏几个链接,而是把题目、题解、思路、复杂度、标签全部串起来,形成一套能直接用来复习和检索的资料库。做完之后,刷题效率明显不一样了,复习的时候也不再像没头苍蝇一样乱翻。
这篇文章就把我整理题解汇总的完整思路、筛选标准、分类方法,以及我踩过的坑全部写出来。不管你是刚开始刷题的新手,还是刷了几百题想系统性复习的老手,这份汇总方法都可以直接参考。我不打算讲大道理,只讲实际操作怎么搞。
1. 题解汇总到底在解决什么问题
1.1 先说说我为什么开始整理题解
刷力扣的人大概都经历过这几个阶段。第一阶段是跟着题号刷,从第1题两数之和开始,刷到第50题左右就会遇到瓶颈,因为题目难度开始波动,有些简单题其实藏了很深的数学原理,有些中等题反而是模板题的变体。第二阶段是开始刷“热题100”,这个阶段大部分人会把题过一遍,但过完之后记忆留存率很低——做了后面的忘了前面的。第三阶段才是真正的分水岭:有些人开始按专题刷,比如专门刷动态规划、专门刷二叉树,但这个阶段如果没有一个系统的整理机制,很容易刷到某个专题深处就迷路。
我的问题出在第二阶段到第三阶段之间。有一次面试前复习,我想找一道“接雨水”的题解对比一下双指针和解法,结果发现我收藏夹里有七八个版本,有的来自题解区高赞,有的来自评论区,还有的是我当时的随手记录,但问题是这些内容散落在不同地方,而且没有一个统一的格式,根本没法快速对比。
所以整理题解汇总,本质上是在解决三个问题:第一,把分散的知识点集中到一处;第二,用统一的格式把不同题的解法标准化,方便对比和回顾;第三,给每道题打上标签,让“刷过但忘了”变成“一搜就能找到”。这个整理过程本身也是一种深度学习,因为你在整理的时候必须重新理解每道题的解法,而不是简单复制粘贴。
1.2 一份好的题解应该长什么样
很多人以为题解就是“题目描述加代码”,这是最大的误解。我在整理过程中逐渐形成一个标准:一份合格的题解必须包含五个部分——题目分析、思路推导、代码实现、复杂度分析、易错点提示。缺了任何一个部分,这道题的复习价值都会大打折扣。
题目分析不是复制题目原文,而是用一句话说清楚“这道题本质在考什么”。比如“腐烂的橘子”这道题,表面看是一个二维数组遍历,本质是多源广度优先搜索,也就是BFS的变种。如果你能在题解里写清楚这个本质,下次遇到类似题目,你就能自动归类到BFS题型的复习列表里。
思路推导是题解的核心。我要求自己必须写清楚“为什么这么想”而不是只写“这么写”。就拿动态规划题来说,光是列出dp数组的定义还不够,还要写出状态转移方程是怎么推出来的,初始化和遍历顺序是怎么确定的。这个部分写多了会显得啰嗦,但复习的时候你会发现,真正能帮你重建思路的,正是这些推导过程。
代码实现方面,我给自己定了一个规矩:每道题只保留一种最优解法的主代码,其他解法只写思路不写完整代码。这样每个题解文件保持精简,不会因为塞了太多代码而变得臃肿。
1.3 汇总不是收藏夹,是知识体系
收藏夹和知识体系最大的区别,在于有没有“索引”。收藏夹是一堆文件的堆叠,你收藏的时候很爽,但要用的时候根本想不起来收藏了什么。知识体系则是在文件之上加了一层结构,让每份内容都有明确的位置和关联。
我当时做汇总时,先建了一个总README作为导航页,里面按标签列出所有题目。然后每道题一个独立Markdown文件,文件名格式统一为“编号_题目名称_难度_核心标签.md”,比如“0994_腐烂的橘子_中等_BFS.md”。这样光看文件名就能对题目有个初步印象,搜索时也方便。
更重要的是,我在每道题的题解文件里都加了一行“关联题目”,把解法思路相近的题链接起来。比如“腐烂的橘子”会关联“岛屿数量”(同样是二维网格遍历)、“地图分析”(同样是多源BFS)、“01矩阵”(同样是BFS分层)。这样当你复习一道题时,可以顺藤摸瓜把一类题全部过一遍,形成网状知识结构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 优秀题解的筛选标准与分类方法论
2.1 我筛选题解用的五条硬标准
力扣题解区的内容鱼龙混杂,高赞不一定高质量,低赞也可能藏着宝藏。我筛选题解时用了五条硬标准,每一条都是踩过坑之后总结出来的。
第一条是“必须讲清楚复杂度”。很多题解代码贴完就结束,连时间复杂度都不分析,这种题解直接淘汰。因为面试时手撕代码,面试官几乎必问复杂度,你平时不养成分析习惯,面试现场就会卡壳。
第二条是“优先选一题多解的题解”。同一个问题,暴力解、优化解、最优解之间的思维跃迁,才是刷题真正的价值所在。一篇只给一种解法的题解,相当于告诉你答案,但没告诉你为什么是这个答案。我遇到最典型的就是“两数之和”,暴力解是O(n²),哈希表是O(n),如果你只看哈希表版本,永远不知道优化是怎么发生的。
第三条是“评论区有真实讨论的优先”。这看起来有点奇怪,但实际操作很有用。力扣题解区高赞评论区经常有关于边界条件、测试用例的讨论,这些讨论往往比题解本身更能暴露问题。比如有些题目用递归解法会栈溢出,评论区一定会有人提到,这些信息能帮你提前避坑。
第四条是“代码风格干净优先”。我见过大量题解代码变量命名是a、b、c,或者一整个函数几十行没有空行,这种题解即使思路正确,阅读成本也太高。我自己整理时会重新整理代码格式,所以这条标准更多是在提醒自己,别把别人的烂代码直接搬进来。
第五条是“必须有可运行性验证”。有些题解思路看起来对,但实际提交会超时或漏边界。我整理每道题时都会亲手提交一次题解里的代码,确保能通过所有测试用例才收录,这条标准花时间最多,但也是整个汇总可信度的根基。
2.2 按数据结构分类:数组、链表、树、图
题解汇总的分类维度有很多种,我最终选择了“数据结构 + 算法范式”双维度分类,而不是简单的难度分类。因为难度的主观性太强,同一道题对不同基础的人来说难度完全不同,但数据结构是客观的,数组就是数组,树就是树。
数据结构维度我分了这些类:数组、字符串、链表、栈与队列、哈希表、树、图、堆、并查集、前缀树。每个大类下面是具体题目。比如树这个大类下,我又有二叉树遍历(前中后序)、二叉搜索树、最近公共祖先、树的序列化等子分类,每道题放进对应的子分类。
选择按数据结构分类的理由是:大多数人的思维习惯是从“看到什么”出发的。看到一道二叉树的题,你会先想到树,然后才想到这题用递归还是迭代。所以数据结构维度是人的直觉入口,适合作为首要索引。
2.3 按算法范式分类:BFS/DFS、DP、回溯、贪心
算法范式维度是我的第二个索引,也是复习时的核心索引。这个维度解决的是“我想练某类算法时,去哪里找题”的问题。
我分得比较粗:枚举与模拟、双指针与滑动窗口、二分查找、排序、贪心、递归与回溯、深度优先搜索DFS、广度优先搜索BFS、动态规划DP、图论算法(最短路径、最小生成树、拓扑排序)、位运算、数学技巧。每类下面同样挂题目。
这个维度的价值在于,它是按“解法”而非“题目”组织的,所以非常适合专题训练。比如连续一周专门练滑动窗口,就把滑动窗口类题目的题解全部过一遍,每道题对比不同窗口收缩策略的异同。这种训练方式比按题号刷效率高很多。
2.4 双维度交叉索引怎么做
两个维度单独存在还不够,真正的汇总精华在于交叉索引。我做法是在每道题的文件里同时写清楚两个维度,比如“腐烂的橘子”既是“图”分类下的题,又是“BFS/DFS”分类下的题。在README总索引里,我用表格维护了双维度交叉关系。
表格大致长这样:
| 题号 | 题目 | 数据结构标签 | 算法标签 | 难度 | 核心思路 |
|---|---|---|---|---|---|
| 0994 | 腐烂的橘子 | 图 | BFS | 中等 | 多源BFS逐层扩散 |
| 0200 | 岛屿数量 | 图 | DFS/BFS | 中等 | 网格遍历染色 |
| 0542 | 01矩阵 | 图 | BFS | 中等 | 多源BFS最近距离 |
这个表格是汇总的导航中枢。复习的时候,我只要看这个表格就能快速定位自己想找的题目,然后点进对应的Markdown文件看详细题解。刚开始建表格可能觉得多了一道工序,但题量大了之后,这张表带来的检索效率提升是巨大的。
3. 用经典题目拆解一份“标杆题解”
3.1 腐烂的橘子:为什么每份优秀题解都绕不开它
“腐烂的橘子”是力扣第994题,热搜词里也有“力扣腐烂的橘子是什么题型”,说明很多人对这题的类型感到困惑。从题型角度说,这是一道典型的图论BFS题,而且不是单源BFS,是多源BFS——所有腐烂的橘子同时开始向外扩散,而不是从一个点开始扩散。这个“同时”是整个题目的关键。
如果你一开始就意识到这是多源BFS,这题就成功了一半。相反,如果被“腐烂扩散”这个比喻带偏,试图用DFS递归去做,就会陷入“每扩散一次要扫一遍全图”的困境,复杂度直接爆炸。
优秀题解会怎么拆解这道题?首先把题目建模,把二维网格看成图,每个格子是节点,相邻的上下左右是边。“腐烂的橘子”就是图中的源点,新鲜橘子是会感染的节点,空格子是不可达区域。然后判断这是BFS还是DFS——题目要求的是“感染所有新鲜橘子所需的最短时间”,而BFS天然是按层扩散的,所以BFS是正解。
3.2 从题目拆解到多解法对比
整理这题题解时,我记录了两种解法思路进行对比,这才是题解的精华所在。
解法一,也是常规解法,使用队列实现多源BFS。第一步遍历整个网格,把所有腐烂橘子入队,同时统计新鲜橘子个数。第二步BFS循环,每次从队列取出当前层的腐烂橘子,向四个方向感染新鲜橘子,被感染的橘子入队并标记为下一层,同时新鲜橘子计数减一。第三步,循环结束后判断新鲜橘子是否全部被感染,如果还有剩余,返回-1,否则返回扩散的层数减一(因为初始腐烂橘子的时间是第0分钟)。
解法二,也是我在评论区看到有人提的“倒推法”,思路更巧妙。既然每个新鲜橘子都会被离它最近的腐烂橘子感染,那么可以先对每个新鲜橘子做一次单源BFS,找出所有新鲜橘子到最近腐烂橘子的最小距离,然后取最大值(如果有新鲜橘子根本到达不了,返回-1)。这个思路在代码上更繁琐,但从思路上反而是“反向多源BFS”的变体。
我推荐的写法是解法一,因为它更贴合“同时扩散”这个语义,而且实现代码更短。但题解里保留了两种思路的对比,复习时可以看到同一个问题可以从正反两个方向切入。
3.3 复杂度分析和边界条件怎么写在题解里
这道题的复杂度分析是整理题解时必须写清楚的部分。多源BFS解法的时间复杂度是O(N×M),N和M分别是网格的行数和列数,因为每个格子最多入队一次。空间复杂度也是O(N×M),最坏情况是队列里装着接近全部格子。
边界条件部分,我写题解时总结了三个容易翻车的点。第一个是网格为空的情况,直接返回0。第二个是新鲜橘子根本不会被感染的情况,比如被空格全包围,BFS结束后新鲜橘子计数不为0,返回-1。第三个是初始状态就没有新鲜橘子,所有格子要么是空格要么已经是烂橘子,这时应该返回0而不是-1,这个点很多人会写错。
关于第三个边界条件,我实际整理时发现至少有三分之一的题解代码在这里出错,说明这个问题有很强的隐蔽性。所以我在题解的“易错点”部分专门用一段话强调了:循环结束后的返回值判断,必须是“如果新鲜橘子计数大于0返回-1,否则返回时间”,而不是“如果时间等于初始值返回-1”。
4. 基于汇总制定的刷题路线
4.1 热题100的正确打开方式
“力扣热题100”是大多数人的入门必刷清单,但很多人刷的方式是“按照榜单顺序从第1题刷到第100题”,这是效率最低的方式。榜单顺序是综合热度和难度排序的,不是按知识点递进排序的,你可能会在第5题遇到一个需要贪心思想的题,在第20题又重新遇到一个更基础的贪心题,知识没有递进性。
我整理完题解汇总之后,根据汇总里的分类标签,把热题100重新按专题拆分了。具体做法是:先按“数组、链表、树、动态规划、图”把100道题分类,然后在每个分类内部,再按难度从简单到困难排序。这样刷完数组类的所有简单题,再刷数组中等的题,知识是有递进的,不会出现题目之间完全孤立的情况。
这个过程做下来,我有一个强烈感受:热题100的价值不在于“100道题”这个数量,而在于它覆盖了约30种核心题型。你把它拆成30个题型,每个题型精做三四道,掌握的广度和深度,远超从头到尾刷一遍的效果。
4.2 按专题刷还是按难度刷:我的路线规划
整理了汇总之后,我给自己规划了一条“三轮复习”路线,这里分享出来供参考。
第一轮,按专题刷基础题。目标是建立知识框架。每个专题先看汇总里的模板题,把模板题的解法吃透,再做两三道变体题。比如链表专题,先做“反转链表”(模板),再做“反转链表II”(区间反转)、“K个一组翻转链表”(进阶)。这个阶段不求数量,求理解。
第二轮,按难度刷综合题。第一轮建立了专题知识,第二轮就要混合起来。这时候主要刷“热题100”里还没刷过的题,以及随手翻难度适中的题。这个阶段的核心目的是训练“看到题目,快速判断属于哪个专题”的能力——这正是面试手撕代码最需要的能力。
第三轮,按错题和薄弱专题刷。汇总里每道题都记录了首次提交时的状态和错误原因,第三轮就专门找出那些没一次通过的题,以及评论区反复强调易错的题,重新做一遍。这个阶段是提分最明显的阶段,因为每个人的薄弱点都不同,只有针对自己的短板补才是最有效率的。
4.3 ICPC/竞赛题解对面试刷题的补充价值
热词里出现了“ICPC网络赛2025题解”“2023ICPC西安区域赛题解”,这引出一个很多人问的问题:竞赛题解到底对面试刷题有没有用?我的看法是,有用,但不能直接拿来作为面试复习主力。竞赛题解的思维强度、代码实现复杂度都远超面试题目,它们更像是一种“拓展训练”而不是“应试训练”。
我在汇总里单独建了一个“竞赛扩展”目录,专门收录一些竞赛题的经典思路。我不会把竞赛题放进主要分类里,而是放在一个独立区域,用于拔高思维。比如ICPC区域赛里有很多关于扫描线、斜率优化DP、平衡树的高级数据结构题,这些内容面试基本不会直接考,但理解它们能帮你在面对困难题时打开思路。
如果你处在面试准备阶段,竞赛题解不需要刷太多,每周挑一道区域赛中等难度的题看看题解思路,开阔一下视野就够了。核心精力还是要放在力扣题库和面试高频题上。如果是为了打竞赛,那赛题复盘才是真正的刚需,要把每次比赛的题解都整理进自己的体系里,形成“错题本+思路本”双份资料。
5. 题解汇总的工具链与落地方法
5.1 用Markdown加Git管理题解仓库
汇总的载体我选择了Markdown文件,配合Git做版本管理。选择Markdown而不是在线文档,理由很简单:离线可读、格式通用、可以本地全文搜索。选择Git的理由更直接——我需要追踪每次改动的内容,比如某道题的题解更新了思路,我希望能看到改动记录,这本身就是一种复习。
仓库结构我设计成这样:
code复制leetcode-notes/
├── README.md # 总索引,含双维度交叉表
├── by-topic/ # 按专题分类的题解
│ ├── 01-array/
│ ├── 02-linked-list/
│ ├── 03-tree/
│ ├── 04-dp/
│ └── ...
├── by-algorithm/ # 按算法范式分类的快捷映射
│ ├── bfs-dfs.md
│ ├── backtracking.md
│ └── ...
├── contest/ # 竞赛补充
└── templates/ # 题解模板和速查表
其中by-topic是主目录,每道题一个文件;by-algorithm是映射文档,只做索引不重复放题解,避免一处修改多处同步的维护问题。这个设计是踩过重复内容不同步的坑之后确定的。
5.2 自动化脚本:批量抓取题目信息
手动为每道题建立Markdown文件,初始化的时候非常痛苦,因为每道题都要填题号、难度、标签、题目链接。我写了一个Python脚本,从力扣的题库接口拉取题目元信息,自动生成文件头。
脚本核心逻辑很简单:
python复制import requests
import json
def fetch_problem_info(slug):
url = "https://leetcode.cn/graphql/"
payload = {
"query": """
query problemInfo($titleSlug: String!) {
question(titleSlug: $titleSlug) {
questionFrontendId
title
difficulty
topicTags { name slug }
translatedTitle
titleSlug
}
}
""",
"variables": {"titleSlug": slug}
}
resp = requests.post(url, json=payload)
data = resp.json()["data"]["question"]
return {
"id": data["questionFrontendId"],
"title": data["translatedTitle"] or data["title"],
"difficulty": data["difficulty"],
"tags": [t["name"] for t in data["topicTags"]],
"slug": data["titleSlug"]
}
def generate_markdown(info):
header = f"""# {info['id']}. {info['title']}
- 难度:{info['difficulty']}
- 标签:{'、'.join(info['tags'])}
- 链接:https://leetcode.cn/problems/{info['slug']}/
## 题目分析
<!-- 在这里写题意本质 -->
## 思路推导
<!-- 在这里写思考过程 -->
## 代码实现
```python
# code here
复杂度分析
- 时间复杂度:
- 空间复杂度:
易错点
关联题目
"""
return header
if name == "main":
slug = input("输入题目 slug:")
info = fetch_problem_info(slug)
print(generate_markdown(info))
code复制
跑一遍脚本就能生成一个带完整头部信息的题解文件,剩下只需要补充思路和代码。这个脚本帮我省了大量重复劳动,也让每道题的元信息完全一致,不会出现有的题没写标签、有的题链接格式不一样的情况。
### 5.3 Obsidian或Notion的标签体系作为辅助
如果你不想折腾Git仓库和脚本,也可以用Obsidian或Notion这类笔记工具来管理题解。Obsidian的特点是本地存储、双链笔记、支持标签系统,非常适合做知识关联;Notion则是线上同步方便,数据库和表格视图对筛选友好。
我试过用Obsidian作为补充工具,核心用法是给每道题打多个标签,然后用标签作为筛选条件,生成动态视图。比如我搜索“#BFS #图”,Obsidian会列出所有同时带这两个标签的笔记,效果和我的Markdown交叉索引表类似,但自动化和动态性更好。
如果你从零开始,我建议先用最朴素的Markdown加Git方案,因为它数据格式透明,不受工具限制,以后想迁移到任何平台都容易。等题量积累到一定程度,再考虑添加Obsidian双链或者数据库视图增强检索体验,不要一开始就把工具链搞得太复杂,容易陷入“整理工具而不是整理题解”的陷阱。
## 6. 整理题解时踩过的坑与避坑指南
### 6.1 常见问题与排查实录
整理题解汇总的过程中,我遇到了不少问题,这里挑几个有代表性的,按“现象 - 原因 - 解决方案”的格式记录下来。
第一个典型问题是“题解文件命名不统一”。初期我有的文件用“题目名称.md”,有的用“题号_题目名称.md”,结果到后面发现按名字搜不到题号,按题号搜不到名字。后来我强制统一命名为“题号_题目名称_难度_核心标签.md”,并写了个脚本把所有文件名批量改掉,从此再没出现过文件名歧义。
第二个问题是“一道题在不同分类下重复收录,内容不同步”。我一开始把一道题同时放在“树”和“DFS”两个目录下,各写了一份题解,后来某次给其中一份补充了“递归改迭代”的优化思路,另一份没同步。这个问题最隐蔽,直到复习时发现两份题解不一样才意识到。解决方案是前文提到的“单文件存放、多维度索引映射”,主文件只存一份,其余分类只存链接。
第三个问题是我初期大量“复制题解区高赞内容”,没有转化成自己的话。这样做的坏处是,复习时看自己“写”的题解,竟然像在看陌生人的文章,思路完全接不上。后来我给自己定下规矩:思路部分一定要用自己的话重写一遍,如果这道题我没完全理解,那就先把题理解透了再写,而不是直接把别人的内容搬过来。这个改动大大提升了汇总的复习价值。
### 6.2 几个容易翻车的细节
有几个细节虽然小,但翻车后很影响使用体验。
第一个是链接失效问题。力扣题解的URL里包含题目的slug,slug一般保持稳定,但如果你复制的是带中文或者带多余参数的URL,一段时间后可能出现访问异常。我统一改用题目页面的标准URL,格式类似“https://leetcode.cn/problems/two-sum/”,把技巧链接都去掉,稳定性明显提升。
第二个是难度标记和标签的跨语言不一致。力扣中文站的标签是中文,英文站是英文,我汇总里统一使用中文站的标签名,这样整理时不用来回翻译。但如果你在英文站刷题,看到的中文标签名对应不上英文名,可能会找不到题。解决办法是在总索引表里同时保留中英文标签,或者固定的“中文名->英文名”映射表。
第三个是代码块语言标注,看似小事但影响很大。我在整理初期顺手把代码块语言标注成“c++”或者“python3”,但不同题解平台渲染Markdown时,语言标注大小写不敏感问题不大,问题是如果用“py”这种不标准写法,某些渲染器会识别失败,代码块没有高亮。统一使用标准写法后,这个问题就解决了:Python用“python”,C++用“cpp”,Java用“java”。
第四个是个“时间戳”问题。题解里标注“最后更新时间”是一个好习惯,因为算法题的题解思路是会长进的,也许半年后你会找到更优解法。我在每道题文件末尾加了一行“最后更新:YYYY-MM-DD”,每次修改内容时更新,久而久之这个时间戳就成了一面镜子,让我知道哪些题很久没碰过了,该复习了。
### 6.3 整理频率和维护节奏
最后说说汇总的维护节奏。我的经验是,不要试图一次性把所有题解整理完,那样工程量太大会中途放弃,而且大量整理反而导致每道题的总结质量下降。更合理的方式是“随刷随整”,每做完一道题就立刻整理对应的题解文件,当天完成,不积压。
我给自己定的节奏是:每天刷两到三道新题,每道题整理十五到二十分钟,周末花半小时整体巡检一次,看看有没有标签遗漏、链接失效、格式不一致的问题。这个节奏不会占用太多刷题时间,又能保证汇总持续更新。
还有一个小技巧,就是“错题重做”后要主动更新题解文件。比如某道题第一次没做出来,看题解才写出来,过了一周重做通过了,我会在题解文件里把“第一次做错原因”和“重做后的理解”记录下来。这种前后对比的记录,是题解汇总里最有价值的资产,因为它是完全属于你个人的成长档案,是任何官方题解都给不了你的。
整理力扣题解汇总这件事,看起来是体力活,实际做下来更像是在给自己搭建一套私人的算法知识库。每一道题的文件,都是你和这道题之间的一次对话记录。等你积累到几百题,再回头看这套汇总,你会发现它已经成了你最趁手的复习工具,也是你面试前最踏实的底气来源。
