简介:这份资源是面向算法面试与编程能力提升的 LeetCode 热题 HOT 100 题解合集,以 C++ 实现为主,适合正在准备校招、社招算法环节或希望系统刷题的开发者。内容围绕高频考点展开,涵盖两数之和的哈希表优化、两数相加的链表进位处理、无重复字符最长子串的滑动窗口,以及寻找两个有序数组中位数等典型题目,并配有完整可运行的代码与复杂度分析。资源为单个 PDF 文件,压缩包约 640KB,篇幅紧凑,便于在电脑或移动设备上随时查阅。目前已有 1638 人学习下载,说明其内容经过一定规模的读者验证。读者可借助它快速掌握数组、链表、字符串等常见题型的解题模板,理解时间与空间复杂度的权衡思路,并在对照代码的过程中积累边界条件处理与调试经验,从而提升刷题效率与面试通过率。
1. 力扣热题100到底该怎么刷:从题目清单到答案模板的完整路径
很多人第一次打开力扣热题100的题目列表,第一反应是“这不就是100道题吗,一天三道一个月搞定”。我当初也这么想,结果第三周卡在“滑动窗口最大值”上整整两天,翻回去看前面刷过的“无重复字符的最长子串”又忘了思路。问题不在题量,在于热题100是一份高频面试题精选,它的价值不是让你“做完”,而是让你把每道题背后的题型模板刻进肌肉记忆。这份清单覆盖了数组、链表、哈希、双指针、动态规划、回溯、图论等核心考点,每道题都对应着大厂面试里反复出现的解题范式。适合谁?准备跳槽的初中级后端、算法岗应届生、以及刷了零散题但缺乏体系的人。接下来的内容,我会把“题目加答案”这件事拆成可复现的流程:怎么分类、怎么记录、怎么验证、怎么避免背了就忘。
2. 热题100的题型分类与答案模板设计
2.1 为什么按题型刷比按题号刷效率高
热题100的官方顺序是混合排列的,第1题“两数之和”是哈希,第2题“两数相加”是链表,第3题“无重复字符的最长子串”是滑动窗口。如果你按这个顺序刷,每道题都在切换思维模式,大脑需要不断重新加载上下文。我自己的做法是先把100道题按解题范式重新分组,同一组连续刷5到8道,让同类题的边界条件在短时间内反复刺激记忆。
常见分组如下:
| 题型 | 热题100中的典型题目 | 核心模板 |
|---|---|---|
| 哈希表 | 两数之和、字母异位词分组、最长连续序列 | 用字典换时间,O(n)降O(n²) |
| 双指针 | 移动零、盛最多水的容器、三数之和 | 左右指针收缩或快慢指针 |
| 滑动窗口 | 无重复字符的最长子串、找到字符串中所有字母异位词 | 右扩左缩,维护窗口内状态 |
| 链表操作 | 相交链表、反转链表、合并两个有序链表 | 哑结点+指针重连 |
| 二叉树遍历 | 中序遍历、最大深度、翻转二叉树 | 递归/迭代两套写法 |
| 动态规划 | 爬楼梯、打家劫舍、最长递增子序列 | 定义dp[i]含义+转移方程 |
| 回溯 | 全排列、子集、电话号码的字母组合 | 选择-递归-撤销 |
| 图论 | 岛屿数量、腐烂的橘子 | BFS/DFS+方向数组 |
这个分组不是绝对的,比如“腐烂的橘子”也可以归到BFS,但关键是你要有一套自己的分类逻辑。分组之后,每道题的答案不再是一段孤立的代码,而是某个模板的具体实例。
2.2 答案模板的四个必备字段
我见过很多人刷题只写代码,过两周回看完全不知道当时怎么想的。一个能复用的答案记录至少包含四个字段:
字段一:题型标签。比如“滑动窗口-定长窗口”或“动态规划-线性DP”。标签要细到能区分“最长递增子序列”和“最长公共子序列”,因为它们的转移方程完全不同。
字段二:核心思路一句话。用中文写清楚“用什么数据结构、在什么条件下、做什么操作”。例如“用哈希表记录每个字符最后出现的位置,左指针跳到重复字符的下一位”。
字段三:可运行代码。Python优先,因为热题100的Python题解在面试中足够用,而且代码短、边界清晰。代码里必须有关键注释,标注每一步在做什么。
字段四:复杂度与边界。时间复杂度、空间复杂度、以及你实际踩过的边界条件。比如“输入为空数组”“只有一个元素”“所有元素相同”。
下面是一个具体的答案模板示例,以“无重复字符的最长子串”为例:
def lengthOfLongestSubstring(s: str) -> int: # 哈希表记录字符最后出现的位置 last_seen = {} left = 0 max_len = 0 for right, ch in enumerate(s): # 如果当前字符出现过,且位置在left之后,收缩左边界 if ch in last_seen and last_seen[ch] >= left: left = last_seen[ch] + 1 # 更新当前字符位置 last_seen[ch] = right # 更新最大长度 max_len = max(max_len, right - left + 1) return max_len逻辑说明:右指针每次移动一位,如果遇到重复字符且该字符上次出现的位置在当前窗口内,就把左指针跳到重复字符的下一位。参数说明:last_seen字典的key是字符,value是索引;left是窗口左边界;max_len维护全局最大值。边界注意:空字符串返回0;字符串全不重复时窗口一直扩展。
2.3 用Anki或Notion建立可检索的答案库
答案写完之后,如果不复习等于白写。我试过三种方式:纯Markdown文件、Notion数据库、Anki卡片。最后稳定下来的是Notion做题库+Anki做抽认的组合。
Notion里建一个表格,字段包括:题号、题目名、题型标签、核心思路、代码、复杂度、首次AC日期、复习次数。每次刷完一道新题就填一行。Notion的好处是可以按题型筛选,比如面试前只看“动态规划”标签下的所有题。
Anki用来做主动回忆。每道题做两张卡:正面是题目描述和题型标签,背面是核心思路和代码截图。复习时先自己想思路,想不出来再看背面。我一般设置间隔为1天、3天、7天、15天,四轮之后基本能条件反射。
这里有一个坑:不要直接把LeetCode的官方题解复制进去。官方题解往往追求最优解,但面试时你需要的是一套你能讲清楚、能手写、能处理边界的解法。我自己的答案库里,有些题用的不是最优解,但胜在逻辑直白、不容易写错。
3. 从题目到答案的实操流程:以腐烂的橘子和基本计算器为例
3.1 腐烂的橘子:BFS模板的完整拆解
“腐烂的橘子”是热题100里图论BFS的代表题。题目大意是:一个网格里有新鲜橘子、腐烂橘子和空格,每分钟腐烂橘子会感染上下左右的新鲜橘子,问多少分钟全部腐烂,如果不能则返回-1。
这道题的核心不是“模拟腐烂过程”,而是多源BFS。所有腐烂橘子同时开始感染,所以初始时要把所有腐烂橘子的坐标加入队列,然后一层一层向外扩散。
from collections import deque def orangesRotting(grid): rows, cols = len(grid), len(grid[0]) queue = deque() fresh = 0 # 统计新鲜橘子数量,并将所有腐烂橘子入队 for r in range(rows): for c in range(cols): if grid[r][c] == 1: fresh += 1 elif grid[r][c] == 2: queue.append((r, c)) # 如果没有新鲜橘子,直接返回0 if fresh == 0: return 0 minutes = 0 directions = [(1,0), (-1,0), (0,1), (0,-1)] while queue and fresh > 0: minutes += 1 # 当前层的腐烂橘子数量 for _ in range(len(queue)): r, c = queue.popleft() for dr, dc in directions: nr, nc = r + dr, c + dc # 边界检查且是新鲜橘子 if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1: grid[nr][nc] = 2 # 标记为腐烂 fresh -= 1 queue.append((nr, nc)) return minutes if fresh == 0 else -1逻辑说明:先遍历一遍网格,把所有腐烂橘子入队,同时统计新鲜橘子数量。然后开始BFS,每一轮代表一分钟,把当前队列里所有腐烂橘子的邻居中新鲜的部分感染并加入下一层。参数说明:queue是双端队列,fresh是剩余新鲜橘子数,minutes是经过的分钟数。边界注意:如果初始没有新鲜橘子,返回0;如果BFS结束后还有新鲜橘子,返回-1。
这道题我踩过的坑:一开始用while queue循环,没有在fresh == 0时提前退出,导致多算了一分钟。后来改成while queue and fresh > 0,并且在循环内先minutes += 1再处理当前层,才正确。
3.2 基本计算器:栈与括号处理的边界
“基本计算器”是热题100里栈应用的硬骨头。题目要求实现一个计算器计算包含加减号和括号的表达式,比如1 + (2 - 3) + 4。
这道题的难点在于括号改变了运算顺序,而且负号可能出现在括号前。常见做法是用一个栈保存括号外的符号和当前结果。
def calculate(s: str) -> int: stack = [] result = 0 num = 0 sign = 1 # 1表示正,-1表示负 for ch in s: if ch.isdigit(): num = num * 10 + int(ch) elif ch in '+-': result += sign * num num = 0 sign = 1 if ch == '+' else -1 elif ch == '(': # 保存当前结果和括号前的符号 stack.append(result) stack.append(sign) result = 0 sign = 1 elif ch == ')': result += sign * num num = 0 # 弹出括号前的符号和结果 result *= stack.pop() # 括号前的符号 result += stack.pop() # 括号前的结果 # 忽略空格 return result + sign * num逻辑说明:遍历字符串,遇到数字累积到num;遇到加减号时把之前的num按sign加到result,然后更新sign;遇到左括号时把当前result和sign压栈,重置result和sign;遇到右括号时先把当前num结算,然后弹出栈顶的sign和result进行合并。参数说明:stack保存括号外的上下文,sign表示当前数字的符号。边界注意:表达式末尾可能没有运算符,所以最后要result + sign * num;空格要跳过。
这道题的血泪经验:我一开始没有在右括号时结算num,导致(2 - 3)里的3被漏掉。后来在')'分支里先result += sign * num再弹栈,才正确。
3.3 用测试用例反向验证答案
写完答案之后,不要只跑LeetCode的默认用例。我习惯自己构造三组用例:最小用例、边界用例、随机用例。
以“腐烂的橘子”为例:
- 最小用例:
[[0]],没有橘子,返回0。 - 边界用例:
[[1]],只有新鲜橘子,返回-1。 - 随机用例:
[[2,1,1],[1,1,0],[0,1,1]],返回4。
以“基本计算器”为例:
- 最小用例:
"1",返回1。 - 边界用例:
"1-(0-1)",返回2。 - 随机用例:
"(1+(4+5+2)-3)+(6+8)",返回23。
这些用例要写进你的答案库,下次复习时先跑一遍,确认代码没有因为环境变化而失效。
4. 热题100刷题中的常见翻车点与排查
4.1 现象:代码在本地跑通,提交后超时
原因:LeetCode的Python环境对时间复杂度敏感,尤其是O(n²)的解法在数据量大时会超时。比如“最长连续序列”如果用排序+遍历,时间复杂度O(n log n),虽然能过但不够优;如果用哈希集合做O(n)查找,才能稳定通过。
解决:先看题目数据范围。如果n在10⁵级别,O(n²)基本会超时。优先考虑哈希、双指针、滑动窗口等O(n)或O(n log n)的解法。我一般会在答案库里标注“必须O(n)”,提醒自己不要用暴力解。
4.2 现象:链表题总是空指针异常
原因:链表题里node.next和node.next.next的访问顺序很容易写反。比如“相交链表”里,如果两个指针没有同时到达末尾,就会在None上调用.next。
解决:所有链表题先写哑结点dummy = ListNode(0),然后用cur = dummy操作。访问node.next之前先判断node是否为None。我自己的习惯是:只要涉及链表,先画三个节点的图,标出每一步指针指向哪里,再写代码。
4.3 现象:动态规划转移方程写对了,但结果不对
原因:dp数组的初始化或遍历顺序错了。比如“打家劫舍”里,dp[i]表示前i个房屋能偷到的最大金额,转移方程是dp[i] = max(dp[i-1], dp[i-2] + nums[i])。如果初始化dp[0] = 0、dp[1] = nums[0],遍历从2开始,结果正确;但如果初始化dp[0] = nums[0],遍历从1开始,就会越界或漏算。
解决:写DP之前先明确三件事:dp数组的含义、初始化条件、遍历顺序。然后在纸上手动推一遍n=1、n=2、n=3的情况,确认每一步的dp值符合预期。我一般会在代码注释里写“dp[i]表示…”,强迫自己把含义写清楚。
4.4 现象:回溯题结果有重复或遗漏
原因:回溯的“选择-递归-撤销”三步里,撤销不彻底或者选择范围不对。比如“全排列”里,如果每次递归都从0开始遍历,需要用一个used数组标记已选元素;如果从start开始遍历,则适用于“组合”类问题。
解决:先判断题目是“排列”还是“组合”。排列需要used数组,组合需要start索引。撤销操作要严格对称:递归前path.append(x),递归后path.pop();递归前used[i] = True,递归后used[i] = False。我自己的检查方法是:在递归函数入口打印path,看每一层的状态是否符合预期。
4.5 现象:面试时手写代码卡壳
原因:平时刷题用IDE的自动补全和调试,手写时连for循环的边界都写不对。
解决:每周至少一次在纸上或白板上手写完整代码,包括import语句和边界判断。我自己的习惯是:刷完一道题后,关掉编辑器,在空白文件里默写一遍,然后跑测试用例。如果默写不出来,说明这道题还没真正掌握,需要重新复习。
5. 用间隔重复和模拟面试把答案变成条件反射
刷完热题100只是第一步,真正让答案变成条件反射的是间隔重复和模拟面试。我自己的节奏是:第一轮按题型刷,每道题写答案模板;第二轮按题号随机刷,只看题目想思路,不写代码;第三轮模拟面试,限时15分钟手写代码并讲解。
这里有一个具体的技巧:用“三句话讲题法”检验掌握程度。拿到一道题,先用三句话讲清楚:用什么数据结构、核心操作是什么、边界条件有哪些。如果三句话讲不完,说明思路还不清晰。比如“腐烂的橘子”可以这样讲:第一句“多源BFS,所有腐烂橘子同时入队”;第二句“每轮感染上下左右的新鲜橘子,层数就是分钟数”;第三句“如果最后还有新鲜橘子返回-1,否则返回分钟数”。
另一个技巧是建立错题本。不是记录做错的题,而是记录“思路卡住超过5分钟”的题。这些题往往代表你的思维盲区。我自己的错题本里,“滑动窗口最大值”卡了三次,后来发现是对单调队列的理解不到位,专门花了一个下午把单调队列的模板背下来,之后再也没卡过。
最后,面试前一周不要刷新题,只复习答案库里的模板和错题本。我一般会把Notion里的题型标签筛选一遍,每个标签挑两道题手写一遍,确保肌肉记忆还在。希望帮到你。
本文还有配套的精品资源,点击获取