LeetCode Hot 100 算法清单
本清单即 LeetCode 热题 HOT 100,共 100 题,业界公认的标准刷题集。分类与顺序合理:按「数据结构由易到难 + 算法思想递进」组织。
针对你的定位(中级 Android 转应用):Android 算法面试以中等题为主。下方每个分类标注了 ⭐(高频必刷)和 △(偏难/低频,可缓刷),按优先级投入精力。
如何使用
- 第一轮:只刷 ⭐ 高频题,建立每类的解题模板(约 40 题),覆盖大部分中小厂。
- 第二轮:补齐其余中等题,冲大厂。
- 第三轮:△ 难题(困难/低频)按目标公司选刷。
- 每类先吃透 1-2 道“母题“模板,其余是变体。重在模板内化而非数量。
进度自测
勾选格式
- [x]。⭐=高频必刷,△=可缓刷。
哈希表(3)
- ⭐ 1. 两数之和(简单)
- 49. 字母异位词分组(中等)
- 128. 最长连续序列(中等)
双指针(4)
- ⭐ 283. 移动零
- ⭐ 11. 盛最多水的容器
- ⭐ 15. 三数之和
- △ 42. 接雨水(困难)
滑动窗口(5)
- ⭐ 3. 无重复字符的最长子串
- 438. 找到字符串中所有字母异位词
- ⭐ 560. 和为 K 的子数组
- △ 239. 滑动窗口最大值(困难)
- △ 76. 最小覆盖子串(困难)
普通数组(5)
- ⭐ 53. 最大子数组和
- ⭐ 56. 合并区间
- ⭐ 189. 轮转数组
- 238. 除自身以外数组的乘积
- △ 41. 缺失的第一个正数(困难)
矩阵(4)
- 73. 矩阵置零
- ⭐ 54. 螺旋矩阵
- 48. 旋转图像
- 240. 搜索二维矩阵 II
链表(14)
- ⭐ 160. 相交链表
- ⭐ 206. 反转链表
- 234. 回文链表
- ⭐ 141. 环形链表
- ⭐ 142. 环形链表 II
- ⭐ 21. 合并两个有序链表
- 2. 两数相加
- ⭐ 19. 删除链表的倒数第 N 个结点
- 24. 两两交换链表中的节点
- △ 25. K 个一组翻转链表(困难)
- 138. 随机链表的复制
- 148. 排序链表
- △ 23. 合并 K 个升序链表(困难)
- ⭐ 146. LRU 缓存
二叉树(15)
- ⭐ 94. 二叉树的中序遍历
- ⭐ 104. 二叉树的最大深度
- ⭐ 226. 翻转二叉树
- ⭐ 101. 对称二叉树
- 543. 二叉树的直径
- ⭐ 102. 二叉树的层序遍历
- 108. 将有序数组转换为二叉搜索树
- ⭐ 98. 验证二叉搜索树
- 230. 二叉搜索树中第 K 小的元素
- 199. 二叉树的右视图
- 114. 二叉树展开为链表
- 105. 从前序与中序遍历序列构造二叉树
- 437. 路径总和 III
- ⭐ 236. 二叉树的最近公共祖先
- △ 124. 二叉树中的最大路径和(困难)
图论(4)
- ⭐ 200. 岛屿数量
- 994. 腐烂的橘子
- ⭐ 207. 课程表
- 208. 实现 Trie (前缀树)
回溯(8)
- ⭐ 46. 全排列
- ⭐ 78. 子集
- 17. 电话号码的字母组合
- 39. 组合总和
- 22. 括号生成
- 79. 单词搜索
- 131. 分割回文串
- △ 51. N 皇后(困难)
二分查找(6)
- ⭐ 35. 搜索插入位置
- 74. 搜索二维矩阵
- ⭐ 34. 在排序数组中查找元素的第一个和最后一个位置
- ⭐ 33. 搜索旋转排序数组
- 153. 寻找旋转排序数组中的最小值
- △ 4. 寻找两个正序数组的中位数(困难)
栈(5)
- ⭐ 20. 有效的括号
- ⭐ 155. 最小栈
- 394. 字符串解码
- ⭐ 739. 每日温度
- △ 84. 柱状图中最大的矩形(困难)
堆(3)
- ⭐ 215. 数组中的第K个最大元素
- ⭐ 347. 前 K 个高频元素
- △ 295. 数据流的中位数(困难)
贪心(4)
- ⭐ 121. 买卖股票的最佳时机
- ⭐ 55. 跳跃游戏
- 45. 跳跃游戏 II
- 763. 划分字母区间
动态规划 - 单维(10)
- ⭐ 70. 爬楼梯
- 118. 杨辉三角
- ⭐ 198. 打家劫舍
- 279. 完全平方数
- ⭐ 322. 零钱兑换
- 139. 单词拆分
- ⭐ 300. 最长递增子序列
- 152. 乘积最大子数组
- 416. 分割等和子集
- △ 32. 最长有效括号(困难)
动态规划 - 多维(5)
- ⭐ 62. 不同路径
- 64. 最小路径和
- ⭐ 5. 最长回文子串
- ⭐ 1143. 最长公共子序列
- △ 72. 编辑距离
技巧题(5)
- ⭐ 136. 只出现一次的数字
- ⭐ 169. 多数元素
- 75. 颜色分类
- 31. 下一个排列
- 287. 寻找重复数
知识点解析
下面按分类讲解每类的核心思想、解题模板、复杂度、常见陷阱。吃透模板比刷题数量更重要。
1. 哈希表
核心思想:用 O(1) 查找换取时间,典型“空间换时间“。统计频次、判断存在、查找配对。
模板(两数之和):边遍历边查表,查 target - num 是否已出现。
fun twoSum(nums: IntArray, target: Int): IntArray {
val map = HashMap<Int, Int>() // value -> index
for (i in nums.indices) {
val need = target - nums[i]
if (map.containsKey(need)) return intArrayOf(map[need]!!, i)
map[nums[i]] = i
}
return intArrayOf()
}
- 49 字母异位词分组:排序后的字符串作 key,或用字符计数数组作 key。
- 128 最长连续序列:用 HashSet,只从“序列起点“(
num-1不存在)开始向后扩展,保证 O(n)。 - 复杂度:多为 O(n) 时间、O(n) 空间。
- 陷阱:128 题必须从起点扩展,否则退化成 O(n²)。
2. 双指针
核心思想:用两个指针协同移动,避免暴力双层循环。常见三类:快慢指针、左右对撞、同向滑动。
模板(左右对撞 - 盛最多水):
fun maxArea(height: IntArray): Int {
var l = 0; var r = height.size - 1; var ans = 0
while (l < r) {
ans = maxOf(ans, minOf(height[l], height[r]) * (r - l))
if (height[l] < height[r]) l++ else r-- // 移动短板
}
return ans
}
- 283 移动零:快慢指针,慢指针指向待填位置。
- 15 三数之和:排序 + 固定一个数 + 双指针,注意去重(跳过重复值)。
- 42 接雨水(△):左右指针 + 维护 leftMax/rightMax;也可用单调栈或 DP。
- 陷阱:三数之和的去重是高频踩坑点;移动短板的贪心要想清楚为什么不移长板。
3. 滑动窗口
核心思想:双指针的进阶,维护一个 [left, right] 窗口,右扩入、左缩出,在 O(n) 内解决子串/子数组问题。
通用模板:
fun slidingWindow(s: String): Int {
val window = HashMap<Char, Int>()
var left = 0; var ans = 0
for (right in s.indices) {
window[s[right]] = (window[s[right]] ?: 0) + 1
while (/* 窗口不满足条件 */ window[s[right]]!! > 1) {
window[s[left]] = window[s[left]]!! - 1 // 左缩
left++
}
ans = maxOf(ans, right - left + 1) // 更新答案
}
return ans
}
- 3 无重复最长子串:窗口内无重复字符。
- 438 找异位词 / 76 最小覆盖子串(△):用计数 + need/valid 计数判断窗口达标。
- 560 和为 K 的子数组:注意这题不是滑窗(有负数),要用前缀和 + 哈希表。
- 239 滑动窗口最大值(△):用单调队列(双端队列),不是普通滑窗。
- 陷阱:560 和 239 是“伪滑窗“,真正要用前缀和/单调队列,别套错模板。
4. 普通数组
核心思想:原地操作、前缀和、边界处理、贪心。看似基础,实则技巧密集。
- 53 最大子数组和:经典 DP / Kadane ——
dp[i] = max(num, dp[i-1]+num),维护全局最大。
fun maxSubArray(nums: IntArray): Int {
var cur = nums[0]; var ans = nums[0]
for (i in 1 until nums.size) {
cur = maxOf(nums[i], cur + nums[i])
ans = maxOf(ans, cur)
}
return ans
}
- 56 合并区间:按起点排序,遍历合并重叠区间。
- 189 轮转数组:三次反转法(整体反转 → 前 k 反转 → 后 n-k 反转),O(1) 空间。
- 238 除自身以外乘积:前缀积 × 后缀积,不用除法。
- 41 缺失第一个正数(△):原地哈希,把
num放到nums[num-1]位置,O(1) 空间是难点。 - 陷阱:41 题要求 O(1) 空间 + O(n) 时间,只能用原地置换;189 的三次反转要记牢。
5. 矩阵
核心思想:二维坐标变换、原地标记、利用有序性。考验空间想象力。
- 73 矩阵置零:用首行首列做标记位,O(1) 额外空间(进阶);简单版用两个 set。
- 54 螺旋矩阵:维护上下左右四个边界,按层收缩遍历。
- 48 旋转图像:先转置(沿主对角线)再水平翻转,等价于顺时针 90°。
- 240 搜索二维矩阵 II:从右上角开始,大则左移、小则下移,O(m+n)。
- 陷阱:54 螺旋矩阵的边界判断和退出条件是 bug 高发区;48 旋转要记住“转置+翻转“组合。
// 48 旋转图像:转置 + 水平翻转
fun rotate(m: Array<IntArray>) {
val n = m.size
for (i in 0 until n) for (j in i + 1 until n) {
val t = m[i][j]; m[i][j] = m[j][i]; m[j][i] = t // 转置
}
for (row in m) row.reverse() // 每行翻转
}
6. 链表
核心思想:指针操作的细节功底。三大利器:虚拟头节点 dummy、快慢指针、递归。面试重中之重。
反转链表(母题,务必背熟):
fun reverseList(head: ListNode?): ListNode? {
var prev: ListNode? = null
var cur = head
while (cur != null) {
val next = cur.next
cur.next = prev // 反转指向
prev = cur
cur = next
}
return prev
}
- dummy 头节点:删除/合并/插入时,在头前加哨兵节点,统一处理头节点边界(21、19、2、24、25)。
- 快慢指针:141/142 判环(Floyd 龟兔,142 求入环点要推数学公式)、19 删倒数第 N 个(快指针先走 N 步)、找中点(148 排序、234 回文)。
- 160 相交链表:双指针走到头切到对方链表,相遇即交点。
- 138 随机链表复制:哈希表映射原节点→新节点,或原地交错复制。
- 148 排序链表:归并排序(快慢找中点 + 合并),O(n log n)。
- 23 合并 K 个(△):小顶堆 或 分治两两合并。
- ⭐ 146 LRU 缓存:哈希表 + 双向链表——这题和你工作里的 LruCache 直接相关,面试高频,必须手写到熟。get/put 都要 O(1),双向链表维护访问顺序,哈希表定位节点。
- 陷阱:142 入环点的数学推导;25 K 个一组翻转的边界;改指针时先存 next 防丢失。
7. 二叉树
核心思想:递归是灵魂。明确“以当前节点为根,左右子树要返回什么“,写好递归三要素(返回值、终止条件、单层逻辑)。
遍历模板:
// 递归中序
fun inorder(root: TreeNode?, res: MutableList<Int>) {
if (root == null) return
inorder(root.left, res)
res.add(root.`val`) // 中序:左-根-右
inorder(root.right, res)
}
// 层序(BFS,用队列)
fun levelOrder(root: TreeNode?): List<List<Int>> {
val res = mutableListOf<List<Int>>()
val queue = ArrayDeque<TreeNode>()
root?.let { queue.add(it) }
while (queue.isNotEmpty()) {
val level = mutableListOf<Int>()
repeat(queue.size) { // 固定本层节点数
val node = queue.removeFirst()
level.add(node.`val`)
node.left?.let { queue.add(it) }
node.right?.let { queue.add(it) }
}
res.add(level)
}
return res
}
- 递归类:104 深度、226 翻转、101 对称、543 直径、110 平衡——都是“后序+返回子树信息“。
- BST 性质:98 验证(中序递增)、230 第 K 小(中序第 k 个)、108 有序数组转 BST(取中点为根)。
- 构造/视图:102 层序、199 右视图(每层最后一个)、105 前序+中序建树、114 展开为链表。
- 路径/祖先:236 最近公共祖先(LCA 递归)、437 路径总和 III(前缀和)、124 最大路径和(△,后序+全局更新)。
- 陷阱:98 验证 BST 不能只比父子,要用上下界或中序;124/543 要区分“经过节点的路径“和“向上返回的贡献“。
8. 图论
核心思想:DFS/BFS 遍历 + 状态标记。网格类把矩阵当图,每个格子是节点。
- 200 岛屿数量:遍历网格,遇到陆地 DFS/BFS 把整片淹掉(标记访问),计数。
- 994 腐烂的橘子:多源 BFS,所有腐烂橘子同时入队,按层扩散记录分钟数。
- 207 课程表:拓扑排序,建图 + 入度,BFS(Kahn)或 DFS 判环。能完成所有课 = 无环。
// 200 岛屿 DFS 淹没
fun dfs(grid: Array<CharArray>, i: Int, j: Int) {
if (i !in grid.indices || j !in grid[0].indices || grid[i][j] != '1') return
grid[i][j] = '0' // 标记已访问
dfs(grid, i+1, j); dfs(grid, i-1, j)
dfs(grid, i, j+1); dfs(grid, i, j-1)
}
- 208 Trie 前缀树:每个节点 26 个子指针 + isEnd 标记,insert/search/startsWith。
- 陷阱:207 必须判环(有环则无法完成);994 是多源 BFS 不是单源。
9. 回溯
核心思想:系统穷举解空间。模板 = 选择 → 递归 → 撤销选择。画出决策树就清晰了。
通用模板:
fun backtrack(path: MutableList<Int>, choices: ...) {
if (/* 满足结束条件 */) { res.add(ArrayList(path)); return }
for (choice in choices) {
if (/* 剪枝:不合法跳过 */) continue
path.add(choice) // 做选择
backtrack(path, ...) // 进入下一层
path.removeAt(path.size - 1) // 撤销选择
}
}
- 排列 vs 组合:全排列(46)用 used 标记;子集(78)/组合(39)用 start 下标避免重复。
- 39 组合总和:可重复选,递归传 i(不是 i+1);需排序剪枝。
- 22 括号生成:左括号数 < n 可加左,右 < 左可加右。
- 79 单词搜索 / 51 N皇后(△):网格/棋盘回溯 + 状态标记 + 回撤。
- 131 分割回文串:每个切点判回文后递归。
- 陷阱:结果要
ArrayList(path)拷贝(否则引用同一个被清空);组合问题用 start 去重,排列用 used。
10. 二分查找
核心思想:有序(或部分有序)空间折半。难在边界:开闭区间、< vs <=、mid±1。坚持一种写法练到肌肉记忆。
模板(闭区间):
fun search(nums: IntArray, target: Int): Int {
var lo = 0; var hi = nums.size - 1 // 闭区间 [lo, hi]
while (lo <= hi) {
val mid = lo + (hi - lo) / 2 // 防溢出
when {
nums[mid] == target -> return mid
nums[mid] < target -> lo = mid + 1
else -> hi = mid - 1
}
}
return -1
}
- 35 搜索插入位置:返回 lo 即插入点。
- 34 查找首尾位置:两次二分找左边界、右边界。
- 33/153 旋转数组:判断哪半有序,再决定收缩方向。
- 74 搜索二维矩阵:展平成一维做二分。
- 4 两正序数组中位数(△):二分切分,O(log(m+n)),Hot100 最难之一,可缓刷。
- 陷阱:
mid = lo + (hi-lo)/2防溢出;旋转数组要先判断有序半区。
11. 栈
核心思想:LIFO 后进先出。匹配类、表达式类用普通栈;“找下一个更大/更小元素“用单调栈。
- 20 有效括号:左括号入栈,右括号匹配栈顶。
- 155 最小栈:辅助栈同步存当前最小值,getMin O(1)。
- 394 字符串解码:双栈(数字栈 + 字符串栈)处理嵌套。
- 739 每日温度:单调递减栈,存下标,出栈时算距离。
- 84 柱状图最大矩形(△):单调递增栈,找每根柱子左右第一个更矮的。
// 739 每日温度:单调栈
fun dailyTemperatures(t: IntArray): IntArray {
val res = IntArray(t.size)
val stack = ArrayDeque<Int>() // 存下标,栈内温度单调递减
for (i in t.indices) {
while (stack.isNotEmpty() && t[i] > t[stack.last()]) {
val j = stack.removeLast()
res[j] = i - j
}
stack.addLast(i)
}
return res
}
- 陷阱:单调栈存下标还是值要想清楚;84 题常加哨兵简化边界。
12. 堆(优先队列)
核心思想:动态维护极值。Top K、数据流问题首选。Kotlin 用 PriorityQueue。
- 215 第 K 大:小顶堆维护 K 个最大(堆顶是第 K 大);或快速选择 O(n) 平均。
- 347 前 K 高频:哈希计数 + 小顶堆按频次,或桶排序 O(n)。
- 295 数据流中位数(△):大顶堆(左半)+ 小顶堆(右半) 对顶,中位数从堆顶取。
// 215 小顶堆维护 K 个最大值
fun findKthLargest(nums: IntArray, k: Int): Int {
val heap = java.util.PriorityQueue<Int>() // 小顶堆
for (n in nums) {
heap.offer(n)
if (heap.size > k) heap.poll() // 弹出最小,留下 K 个最大
}
return heap.peek()
}
- 陷阱:求第 K 大用小顶堆(反直觉),求第 K 小用大顶堆;295 双堆要维护大小平衡。
13. 贪心
核心思想:每步取局部最优,期望达到全局最优。关键是证明贪心成立(否则要用 DP)。
- 121 买卖股票:遍历记录历史最低价,每天算“今天卖“的最大利润。
- 55 跳跃游戏:维护能到达的最远下标,遍历中若当前位置超过最远则失败。
- 45 跳跃游戏 II:贪心找每一步能跳到的最远范围,边界处步数+1。
- 763 划分字母区间:记录每个字母最后出现位置,遍历扩展当前区间右界,到界则切分。
// 55 跳跃游戏
fun canJump(nums: IntArray): Boolean {
var farthest = 0
for (i in nums.indices) {
if (i > farthest) return false // 到不了 i
farthest = maxOf(farthest, i + nums[i])
}
return true
}
- 陷阱:贪心不是万能,要确认局部最优能推全局;55/45 的“最远可达“思路要分清。
14. 动态规划
核心思想:把大问题拆成重叠子问题,用 dp 数组存历史结果避免重算。五步:定义状态 → 状态转移方程 → 初始化 → 遍历顺序 → 返回值。DP 是面试天花板,务必吃透母题。
单维 DP
- 70 爬楼梯:
dp[i] = dp[i-1] + dp[i-2](斐波那契),DP 入门母题。 - 198 打家劫舍:
dp[i] = max(dp[i-1], dp[i-2] + nums[i])(偷或不偷)。 - 322 零钱兑换:完全背包,
dp[amount] = min(dp[amount - coin] + 1)。 - 300 最长递增子序列:
dp[i]= 以 i 结尾的 LIS,O(n²);进阶贪心+二分 O(n log n)。 - 139 单词拆分:
dp[i]= 前 i 个字符能否拆分。 - 152 乘积最大子数组:同时维护最大、最小(负负得正)。
- 416 分割等和子集:01 背包,目标 sum/2。
- 32 最长有效括号(△):
dp[i]= 以 i 结尾的最长有效长度,转移较绕。
// 322 零钱兑换(完全背包)
fun coinChange(coins: IntArray, amount: Int): Int {
val dp = IntArray(amount + 1) { amount + 1 } // 初始化为不可达
dp[0] = 0
for (i in 1..amount)
for (c in coins)
if (c <= i) dp[i] = minOf(dp[i], dp[i - c] + 1)
return if (dp[amount] > amount) -1 else dp[amount]
}
多维 DP
- 62 不同路径:
dp[i][j] = dp[i-1][j] + dp[i][j-1](网格路径母题)。 - 64 最小路径和:同上取 min + 当前值。
- 5 最长回文子串:区间 DP,
dp[i][j]= s[i..j] 是否回文;或中心扩展。 - 1143 最长公共子序列(LCS):
dp[i][j],相等 +1,否则取两侧 max——序列型 DP 母题。 - 72 编辑距离(△):
dp[i][j]= word1 前 i 转 word2 前 j 的最少操作(增删改三选一取 min)。
// 1143 LCS:序列型 DP 母题
fun longestCommonSubsequence(a: String, b: String): Int {
val dp = Array(a.length + 1) { IntArray(b.length + 1) }
for (i in 1..a.length) for (j in 1..b.length) {
dp[i][j] = if (a[i-1] == b[j-1]) dp[i-1][j-1] + 1
else maxOf(dp[i-1][j], dp[i][j-1])
}
return dp[a.length][b.length]
}
- 陷阱:dp 数组大小常为
n+1(含空状态);初始化和遍历顺序错是主要 bug;先吃透 LCS、01背包、完全背包三个母题,其余是变体。
15. 技巧题
核心思想:发现问题的特殊性质,用位运算、数学、特定结构巧解。考察活用能力。
- 136 只出现一次的数字:异或,相同抵消,剩下的就是答案,O(1) 空间。
- 169 多数元素:摩尔投票,众数计数抵消法,O(1) 空间。
- 75 颜色分类:荷兰国旗,三指针(0/1/2)一次遍历原地排序。
- 31 下一个排列:从右找第一个升序对,交换 + 反转后缀。
- 287 寻找重复数:把数组看成链表,快慢指针找环(Floyd),不修改数组、O(1) 空间。
// 136 只出现一次:异或
fun singleNumber(nums: IntArray): Int {
var x = 0
for (n in nums) x = x xor n // a^a=0, a^0=a
return x
}
- 陷阱:这类题“知道就秒、不知道想破头“,务必记住每题的 trick(异或、摩尔投票、Floyd 判环)。
刷题策略总结
高频母题(背到能默写)
反转链表、二叉树三种遍历 + 层序、二分模板、回溯模板、滑动窗口模板、01背包/完全背包/LCS。这 7 类模板覆盖了 Hot 100 的 70%。
针对中级 Android 的优先级
- 第一梯队(必刷,中小厂够用):本文 ⭐ 标记的 ~40 题。尤其 146 LRU(和 LruCache 直接相关,Android 超高频)、链表全套、二叉树遍历、岛屿数量、爬楼梯/打家劫舍/零钱兑换、买卖股票、20 有效括号。
- 第二梯队(冲大厂):其余中等题。
- 第三梯队(△ 困难,选刷):42 接雨水、4 中位数、23 合并K个、25 K个一组、84 柱状图、124 最大路径和、51 N皇后、72 编辑距离。时间不够可跳过,面到再说。
方法论
- 分类刷,不要顺序刷:一次攻一个专题,内化模板。
- 二刷三刷:第一遍看题解理解,第二遍独立写,第三遍默写模板。
- 手写 + 口述复杂度:面试要边写边讲思路和时间/空间复杂度。
- 限时:中等题目标 20-30 分钟内 AC。
- 错题本:记录卡壳点和 trick,面试前快速过。