LeetCode Hot 100 精讲
本章目标:按主题分组梳理 LeetCode Hot 100 高频面试题,每题给出核心思路、关键代码与复杂度。
你将学到
- Hot 100 的按主题分类
- 每道题的核心思路与代码模板
- 快速识别题型的方法
- 高频题的最优解
使用方法
- 第一遍:看一句话思路,尝试自己写。
- 第二遍:对比代码,理解差异。
- 第三遍:闭卷默写,限时 20 分钟一道 Medium。
哈希表
1 · 两数之和(Easy)
思路:哈希表存 target-num,扫一遍即可。
def twoSum(nums, target):
seen = {}
for i, num in enumerate(nums):
if target - num in seen:
return [seen[target - num], i]
seen[num] = i时间 O(n),空间 O(n)。
变体:
- 两数之和 II(有序数组):双指针 O(n)。
- 三数之和:排序 + 双指针 O(n²)。
- 四数之和:排序 + 双指针 + 剪枝 O(n³)。
49 · 字母异位词分组(Medium)
思路:排序后的字符串做 key,原字符串归到对应组。
from collections import defaultdict
def groupAnagrams(strs):
groups = defaultdict(list)
for s in strs:
key = ''.join(sorted(s))
groups[key].append(s)
return list(groups.values())时间 O(n·k log k),n 是字符串数,k 是最长串长度。
优化:用计数(26 维向量)做 key,时间 O(n·k)。
128 · 最长连续序列(Medium)
思路:Set 存全部,只从序列起点(num-1 不在 Set 中)开始数。
def longestConsecutive(nums):
num_set = set(nums)
longest = 0
for num in num_set:
if num - 1 not in num_set: # 起点
cur = num
length = 1
while cur + 1 in num_set:
cur += 1
length += 1
longest = max(longest, length)
return longest时间 O(n)(每个元素最多访问 2 次)。
双指针
283 · 移动零(Easy)
思路:快慢指针交换,慢指针指向下一个非零应放的位置。
def moveZeroes(nums):
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1时间 O(n),空间 O(1)。
11 · 盛最多水的容器(Medium)
思路:对撞指针,每次移动较短的一边(移动较长边不可能更大)。
def maxArea(height):
l, r = 0, len(height) - 1
ans = 0
while l < r:
ans = max(ans, min(height[l], height[r]) * (r - l))
if height[l] < height[r]:
l += 1
else:
r -= 1
return ans时间 O(n),空间 O(1)。
15 · 三数之和(Medium)
思路:排序 + 固定一个数 + 双指针找两数之和。
def threeSum(nums):
nums.sort()
ans = []
n = len(nums)
for i in range(n - 2):
if i > 0 and nums[i] == nums[i-1]: continue # 去重
l, r = i + 1, n - 1
while l < r:
s = nums[i] + nums[l] + nums[r]
if s < 0: l += 1
elif s > 0: r -= 1
else:
ans.append([nums[i], nums[l], nums[r]])
while l < r and nums[l] == nums[l+1]: l += 1 # 去重
while l < r and nums[r] == nums[r-1]: r -= 1
l += 1; r -= 1
return ans时间 O(n²),空间 O(1)(不含输出)。
42 · 接雨水(Hard)
思路 1(双指针):维护 leftMax、rightMax,从较小端推进。
def trap(height):
l, r = 0, len(height) - 1
lmax = rmax = 0
ans = 0
while l < r:
if height[l] < height[r]:
if height[l] >= lmax: lmax = height[l]
else: ans += lmax - height[l]
l += 1
else:
if height[r] >= rmax: rmax = height[r]
else: ans += rmax - height[r]
r -= 1
return ans时间 O(n),空间 O(1)。
思路 2(单调栈):维护递减栈,遇到更高柱子时弹出栈顶并计算。
思路 3(DP 预处理):预处理 leftMax[]、rightMax[],逐项求和。
滑动窗口
3 · 无重复字符的最长子串(Medium)
思路:窗口 + 哈希,遇重复缩左。
def lengthOfLongestSubstring(s):
seen = {}
l = 0
ans = 0
for r, c in enumerate(s):
if c in seen and seen[c] >= l:
l = seen[c] + 1
seen[c] = r
ans = max(ans, r - l + 1)
return ans时间 O(n),空间 O(min(n, 字符集大小))。
438 · 找到所有异位词(Medium)
思路:定长窗口比较频率(用计数数组或排序后比较)。
from collections import Counter
def findAnagrams(s, p):
ans = []
pc = Counter(p)
sc = Counter()
k = len(p)
for i, c in enumerate(s):
sc[c] += 1
if i >= k:
sc[s[i-k]] -= 1
if sc[s[i-k]] == 0: del sc[s[i-k]]
if sc == pc:
ans.append(i - k + 1)
return ans时间 O(n),空间 O(字符集大小)。
子串
560 · 和为 K 的子数组(Medium)
思路:前缀和 + 哈希。prefix[j] - prefix[i] = k ⟺ prefix[i] = prefix[j] - k。
from collections import defaultdict
def subarraySum(nums, k):
cnt = defaultdict(int)
cnt[0] = 1
prefix = 0
ans = 0
for num in nums:
prefix += num
ans += cnt[prefix - k]
cnt[prefix] += 1
return ans时间 O(n),空间 O(n)。
注意:不能用滑动窗口(数组可能有负数)。
239 · 滑动窗口最大值(Hard)
思路:单调递减队列,队首始终是窗口最大值。
from collections import deque
def maxSlidingWindow(nums, k):
dq = deque() # 存索引
ans = []
for i, num in enumerate(nums):
while dq and nums[dq[-1]] <= num:
dq.pop()
dq.append(i)
if dq[0] <= i - k:
dq.popleft()
if i >= k - 1:
ans.append(nums[dq[0]])
return ans时间 O(n),空间 O(k)。
76 · 最小覆盖子串(Hard)
思路:不定长窗口 + 计数。
from collections import Counter
def minWindow(s, t):
need = Counter(t)
missing = len(t)
l = 0
start, end = 0, float('inf')
for r, c in enumerate(s):
if need[c] > 0: missing -= 1
need[c] -= 1
while missing == 0:
if r - l < end - start:
start, end = l, r
need[s[l]] += 1
if need[s[l]] > 0: missing += 1
l += 1
return s[start:end+1] if end != float('inf') else ""时间 O(|s| + |t|),空间 O(字符集大小)。
普通数组
53 · 最大子数组和(Medium)
思路:DP 或贪心,维护以当前元素结尾的最大和。
def maxSubArray(nums):
cur = ans = nums[0]
for num in nums[1:]:
cur = max(num, cur + num)
ans = max(ans, cur)
return ans时间 O(n),空间 O(1)。
变体:返回子数组本身?维护起止下标。
56 · 合并区间(Medium)
思路:按左端点排序后扫描。
def merge(intervals):
intervals.sort()
ans = [intervals[0]]
for s, e in intervals[1:]:
if s <= ans[-1][1]:
ans[-1][1] = max(ans[-1][1], e)
else:
ans.append([s, e])
return ans时间 O(n log n),空间 O(1)(不含输出)。
189 · 轮转数组(Medium)
思路:三次反转。
def rotate(nums, k):
n = len(nums)
k %= n
nums.reverse()
nums[:k] = reversed(nums[:k])
nums[k:] = reversed(nums[k:])时间 O(n),空间 O(1)。
238 · 除自身以外数组的乘积(Medium)
思路:左右前缀积。
def productExceptSelf(nums):
n = len(nums)
ans = [1] * n
left = 1
for i in range(n):
ans[i] = left
left *= nums[i]
right = 1
for i in range(n-1, -1, -1):
ans[i] *= right
right *= nums[i]
return ans时间 O(n),空间 O(1)(不含输出)。
41 · 缺失的第一个正数(Hard)
思路:原地哈希。把 nums[i] 放到下标 nums[i]-1 的位置。
def firstMissingPositive(nums):
n = len(nums)
for i in range(n):
while 1 <= nums[i] <= n and nums[nums[i]-1] != nums[i]:
nums[nums[i]-1], nums[i] = nums[i], nums[nums[i]-1]
for i in range(n):
if nums[i] != i + 1:
return i + 1
return n + 1时间 O(n),空间 O(1)。
链表
160 · 相交链表(Easy)
思路:双指针走完自己走对方的路径,必然在交点相遇。
def getIntersectionNode(headA, headB):
pa, pb = headA, headB
while pa != pb:
pa = pa.next if pa else headB
pb = pb.next if pb else headA
return pa时间 O(m+n),空间 O(1)。
206 · 反转链表(Easy)
def reverseList(head):
prev, cur = None, head
while cur:
nxt = cur.next
cur.next = prev
prev, cur = cur, nxt
return prev时间 O(n),空间 O(1)。
234 · 回文链表(Easy)
思路:快慢找中点 + 翻转后半 + 比较。
141 · 环形链表(Easy)
思路:快慢指针,相遇则有环。
142 · 环形链表 II(Medium)
思路:Floyd 找入口。相遇后,一指针回到头,两指针同速前进再次相遇即为入口。
def detectCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
# 找入口
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
return None21 · 合并两个有序链表(Easy)
思路:虚拟头节点 + 双指针。
2 · 两数相加(Medium)
思路:模拟进位。
19 · 删除倒数第 N 个节点(Medium)
思路:快慢指针,快先走 N 步。
25 · K 个一组翻转链表(Hard)
思路:分段翻转,注意前后连接。
138 · 随机链表的复制(Medium)
思路:哈希映射或节点拼接(在原节点后插入复制节点)。
148 · 排序链表(Hard)
思路:归并排序。O(n log n) 时间,O(log n) 空间(递归栈)。
23 · 合并 K 个升序链表(Hard)
思路:小根堆,每次弹出最小元素的链表。
import heapq
def mergeKLists(lists):
heap = []
for i, node in enumerate(lists):
if node:
heapq.heappush(heap, (node.val, i, node))
dummy = ListNode()
cur = dummy
while heap:
val, i, node = heapq.heappop(heap)
cur.next = node
cur = cur.next
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next时间 O(N log k),N 是总节点数,k 是链表数。
146 · LRU 缓存(Medium)
详见 LRU 缓存专题。
二叉树
94 · 中序遍历(Easy)
迭代版(用栈):
def inorderTraversal(root):
ans, stack = [], []
while root or stack:
while root:
stack.append(root)
root = root.left
root = stack.pop()
ans.append(root.val)
root = root.right
return ans104 · 最大深度(Easy)
def maxDepth(root):
if not root: return 0
return 1 + max(maxDepth(root.left), maxDepth(root.right))226 · 翻转二叉树(Easy)
def invertTree(root):
if not root: return None
root.left, root.right = invertTree(root.right), invertTree(root.left)
return root101 · 对称二叉树(Easy)
def isSymmetric(root):
def check(a, b):
if not a and not b: return True
if not a or not b: return False
return a.val == b.val and check(a.left, b.right) and check(a.right, b.left)
return check(root.left, root.right) if root else True543 · 二叉树直径(Easy)
思路:DFS 返回深度,过程中维护全局最大直径。
def diameterOfBinaryTree(root):
ans = 0
def depth(node):
nonlocal ans
if not node: return 0
l = depth(node.left)
r = depth(node.right)
ans = max(ans, l + r)
return 1 + max(l, r)
depth(root)
return ans102 · 层序遍历(Medium)
from collections import deque
def levelOrder(root):
if not root: return []
ans = []
q = deque([root])
while q:
level = []
for _ in range(len(q)):
node = q.popleft()
level.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
ans.append(level)
return ans108 · 有序数组转 BST(Medium)
思路:取中点为根,递归左右子数组。
98 · 验证 BST(Medium)
思路:中序遍历应严格递增,或传递上下界。
def isValidBST(root):
def check(node, lo, hi):
if not node: return True
if not (lo < node.val < hi): return False
return check(node.left, lo, node.val) and check(node.right, node.val, hi)
return check(root, float('-inf'), float('inf'))230 · BST 第 K 小(Medium)
思路:中序遍历到第 K 个。
199 · 二叉树右视图(Medium)
思路:层序取每层最后。
114 · 二叉树展开为链表(Medium)
思路:右子树接到左子树最右,左子树作为新右子树,递归。
105 · 前序+中序构造二叉树(Medium)
思路:前序定根,中序分左右。
437 · 路径总和 III(Medium)
思路:前缀和 + DFS。
236 · 最近公共祖先(Medium)
def lowestCommonAncestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right: return root
return left or right124 · 最大路径和(Hard)
思路:DFS 维护全局最大,返回"单边最大贡献"。
def maxPathSum(root):
ans = float('-inf')
def gain(node):
nonlocal ans
if not node: return 0
l = max(gain(node.left), 0)
r = max(gain(node.right), 0)
ans = max(ans, node.val + l + r)
return node.val + max(l, r)
gain(root)
return ans图论
200 · 岛屿数量(Medium)
思路:DFS/BFS 标记。遍历每个格子,遇 1 启动 DFS 把连通的 1 全部置 0。
def numIslands(grid):
if not grid: return 0
m, n = len(grid), len(grid[0])
count = 0
def dfs(i, j):
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1':
return
grid[i][j] = '0'
for di, dj in [(1,0),(-1,0),(0,1),(0,-1)]:
dfs(i+di, j+dj)
for i in range(m):
for j in range(n):
if grid[i][j] == '1':
count += 1
dfs(i, j)
return count994 · 腐烂的橘子(Medium)
思路:多源 BFS。
207 · 课程表(Medium)
思路:拓扑排序判环(BFS 入度法或 DFS 三色标记)。
from collections import deque, defaultdict
def canFinish(numCourses, prerequisites):
g = defaultdict(list)
indeg = [0] * numCourses
for a, b in prerequisites:
g[b].append(a)
indeg[a] += 1
q = deque([i for i in range(numCourses) if indeg[i] == 0])
count = 0
while q:
node = q.popleft()
count += 1
for nei in g[node]:
indeg[nei] -= 1
if indeg[nei] == 0:
q.append(nei)
return count == numCourses208 · 实现 Trie(Medium)
思路:字典树。
class Trie:
def __init__(self):
self.children = {}
self.is_end = False
def insert(self, word):
node = self
for c in word:
if c not in node.children:
node.children[c] = Trie()
node = node.children[c]
node.is_end = True
def search(self, word):
node = self._find(word)
return node is not None and node.is_end
def startsWith(self, prefix):
return self._find(prefix) is not None
def _find(self, prefix):
node = self
for c in prefix:
if c not in node.children: return None
node = node.children[c]
return node回溯
46 · 全排列(Medium)
def permute(nums):
ans = []
def backtrack(path, used):
if len(path) == len(nums):
ans.append(path[:])
return
for i in range(len(nums)):
if not used[i]:
used[i] = True
path.append(nums[i])
backtrack(path, used)
path.pop()
used[i] = False
backtrack([], [False]*len(nums))
return ans78 · 子集(Medium)
def subsets(nums):
ans = []
def backtrack(start, path):
ans.append(path[:])
for i in range(start, len(nums)):
path.append(nums[i])
backtrack(i+1, path)
path.pop()
backtrack(0, [])
return ans17 · 电话号码字母组合(Medium)
思路:回溯 + 数字到字母的映射。
39 · 组合总和(Medium)
思路:回溯,可重复选(递归时 i 不 +1)。
22 · 括号生成(Medium)
思路:回溯 + 剪枝(左括号数 < n,右括号数 < 左括号数)。
def generateParenthesis(n):
ans = []
def backtrack(s, l, r):
if len(s) == 2 * n:
ans.append(s)
return
if l < n: backtrack(s + '(', l + 1, r)
if r < l: backtrack(s + ')', l, r + 1)
backtrack('', 0, 0)
return ans79 · 单词搜索(Medium)
思路:DFS + 回溯。从每个格子尝试匹配首字母。
131 · 分割回文串(Medium)
思路:回溯切割 + 回文判断。
51 · N 皇后(Hard)
思路:逐行回溯,对角线去重(用集合记录列、主对角、副对角)。
def solveNQueens(n):
ans = []
cols, diag1, diag2 = set(), set(), set()
board = [['.'] * n for _ in range(n)]
def backtrack(r):
if r == n:
ans.append([''.join(row) for row in board])
return
for c in range(n):
if c in cols or r-c in diag1 or r+c in diag2:
continue
board[r][c] = 'Q'
cols.add(c); diag1.add(r-c); diag2.add(r+c)
backtrack(r + 1)
board[r][c] = '.'
cols.discard(c); diag1.discard(r-c); diag2.discard(r+c)
backtrack(0)
return ans二分查找
35 · 搜索插入位置(Easy)
思路:标准 lowerBound。
def searchInsert(nums, target):
l, r = 0, len(nums)
while l < r:
m = (l + r) // 2
if nums[m] < target: l = m + 1
else: r = m
return l74 · 搜索二维矩阵(Medium)
思路:展平后二分。
34 · 查找范围(Medium)
思路:左右边界各二分一次。
def searchRange(nums, target):
def find_first():
l, r = 0, len(nums)
while l < r:
m = (l + r) // 2
if nums[m] < target: l = m + 1
else: r = m
return l
def find_last():
l, r = 0, len(nums)
while l < r:
m = (l + r) // 2
if nums[m] <= target: l = m + 1
else: r = m
return l - 1
first = find_first()
if first == len(nums) or nums[first] != target:
return [-1, -1]
return [first, find_last()]33 · 搜索旋转数组(Medium)
思路:判断哪半有序。
def search(nums, target):
l, r = 0, len(nums) - 1
while l <= r:
m = (l + r) // 2
if nums[m] == target: return m
if nums[l] <= nums[m]: # 左半有序
if nums[l] <= target < nums[m]: r = m - 1
else: l = m + 1
else: # 右半有序
if nums[m] < target <= nums[r]: l = m + 1
else: r = m - 1
return -1153 · 旋转数组最小值(Medium)
思路:二分比较 mid 与 right。
4 · 寻找两个正序数组中位数(Hard)
思路:二分切分点,O(log(min(m, n)))。
栈
20 · 有效的括号(Easy)
def isValid(s):
pair = {')': '(', ']': '[', '}': '{'}
stack = []
for c in s:
if c in '([{': stack.append(c)
elif not stack or stack.pop() != pair[c]: return False
return not stack155 · 最小栈(Easy)
思路:辅助栈同步维护当前最小。
394 · 字符串解码(Medium)
思路:栈处理嵌套(数字 + 字符串)。
739 · 每日温度(Medium)
思路:单调递减栈。
def dailyTemperatures(temperatures):
n = len(temperatures)
ans = [0] * n
stack = []
for i in range(n):
while stack and temperatures[stack[-1]] < temperatures[i]:
j = stack.pop()
ans[j] = i - j
stack.append(i)
return ans84 · 柱状图最大矩形(Hard)
思路:单调递增栈,弹出时计算以栈顶为高的矩形。
def largestRectangleArea(heights):
stack = []
ans = 0
heights.append(0) # 哨兵
for i in range(len(heights)):
while stack and heights[stack[-1]] > heights[i]:
h = heights[stack.pop()]
w = i if not stack else i - stack[-1] - 1
ans = max(ans, h * w)
stack.append(i)
return ans堆
215 · 第 K 大(Medium)
思路:小根堆维护 K 个元素,或快速选择。
import heapq
def findKthLargest(nums, k):
heap = nums[:k]
heapq.heapify(heap)
for num in nums[k:]:
if num > heap[0]:
heapq.heapreplace(heap, num)
return heap[0]快速选择(平均 O(n)):
import random
def findKthLargest(nums, k):
def partition(l, r):
pivot = nums[r]
i = l
for j in range(l, r):
if nums[j] > pivot: # 大到小
nums[i], nums[j] = nums[j], nums[i]
i += 1
nums[i], nums[r] = nums[r], nums[i]
return i
l, r = 0, len(nums) - 1
k -= 1
while True:
p = partition(l, r)
if p == k: return nums[p]
elif p < k: l = p + 1
else: r = p - 1347 · 前 K 高频(Medium)
思路:频率统计 + 堆 / 桶排序。
295 · 数据流中位数(Hard)
思路:大根堆 + 小根堆,左边最大 ≤ 右边最小。
import heapq
class MedianFinder:
def __init__(self):
self.lo = [] # 大根堆(存负值)
self.hi = [] # 小根堆
def addNum(self, num):
heapq.heappush(self.lo, -num)
heapq.heappush(self.hi, -heapq.heappop(self.lo))
if len(self.lo) < len(self.hi):
heapq.heappush(self.lo, -heapq.heappop(self.hi))
def findMedian(self):
if len(self.lo) > len(self.hi):
return -self.lo[0]
return (-self.lo[0] + self.hi[0]) / 2贪心
121 · 买卖股票最佳时机(Easy)
思路:维护历史最小值。
def maxProfit(prices):
min_price = float('inf')
ans = 0
for p in prices:
min_price = min(min_price, p)
ans = max(ans, p - min_price)
return ans55 · 跳跃游戏(Medium)
思路:维护最远可达。
def canJump(nums):
far = 0
for i in range(len(nums)):
if i > far: return False
far = max(far, i + nums[i])
return True45 · 跳跃游戏 II(Medium)
思路:贪心最少跳跃,维护当前轮最远。
763 · 划分字母区间(Medium)
思路:记录每个字母最后位置。
动态规划
70 · 爬楼梯(Easy)
思路:dp[i] = dp[i-1] + dp[i-2],本质斐波那契。可空间压缩到 O(1)。
118 · 杨辉三角(Easy)
def generate(numRows):
ans = [[1]]
for i in range(1, numRows):
row = [1]
for j in range(1, i):
row.append(ans[-1][j-1] + ans[-1][j])
row.append(1)
ans.append(row)
return ans198 · 打家劫舍(Medium)
def rob(nums):
prev, curr = 0, 0
for num in nums:
prev, curr = curr, max(curr, prev + num)
return curr279 · 完全平方数(Medium)
思路:完全背包。
def numSquares(n):
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
j = 1
while j * j <= i:
dp[i] = min(dp[i], dp[i - j*j] + 1)
j += 1
return dp[n]322 · 零钱兑换(Medium)
思路:完全背包。
def coinChange(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if coin <= i:
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1139 · 单词拆分(Medium)
思路:dp[i] 表示 s[:i] 能否拆分。
def wordBreak(s, wordDict):
word_set = set(wordDict)
dp = [False] * (len(s) + 1)
dp[0] = True
for i in range(1, len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[len(s)]300 · 最长递增子序列(Medium)
O(n²) DP:
def lengthOfLIS(nums):
if not nums: return 0
dp = [1] * len(nums)
for i in range(1, len(nums)):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)O(n log n) 贪心 + 二分:
from bisect import bisect_left
def lengthOfLIS(nums):
tails = []
for num in nums:
pos = bisect_left(tails, num)
if pos == len(tails): tails.append(num)
else: tails[pos] = num
return len(tails)152 · 乘积最大子数组(Medium)
思路:维护最大和最小(负负得正)。
def maxProduct(nums):
ans = cur_max = cur_min = nums[0]
for num in nums[1:]:
candidates = (num, cur_max * num, cur_min * num)
cur_max = max(candidates)
cur_min = min(candidates)
ans = max(ans, cur_max)
return ans416 · 分割等和子集(Medium)
思路:0-1 背包。详见 背包专题。
32 · 最长有效括号(Hard)
思路:DP 或栈。
def longestValidParentheses(s):
stack = [-1]
ans = 0
for i, c in enumerate(s):
if c == '(':
stack.append(i)
else:
stack.pop()
if not stack:
stack.append(i)
else:
ans = max(ans, i - stack[-1])
return ans多维 DP
62 · 不同路径(Medium)
def uniquePaths(m, n):
dp = [[1] * n for _ in range(m)]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]可压缩到 O(n) 空间。
64 · 最小路径和(Medium)
def minPathSum(grid):
m, n = len(grid), len(grid[0])
dp = [[0] * n for _ in range(m)]
dp[0][0] = grid[0][0]
for j in range(1, n): dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m):
dp[i][0] = dp[i-1][0] + grid[i][0]
for j in range(1, n):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
return dp[m-1][n-1]5 · 最长回文子串(Medium)
思路:中心扩展。
def longestPalindrome(s):
def expand(l, r):
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1; r += 1
return l + 1, r - 1
start = end = 0
for i in range(len(s)):
l1, r1 = expand(i, i)
l2, r2 = expand(i, i + 1)
if r1 - l1 > end - start: start, end = l1, r1
if r2 - l2 > end - start: start, end = l2, r2
return s[start:end+1]1143 · 最长公共子序列(Medium)
详见 动态规划。
72 · 编辑距离(Medium)
详见 动态规划。
123 · 买卖股票 III(Hard)
思路:三维 DP(天数、交易次数、持有状态)。详见 股票系列。
技巧
136 · 只出现一次的数字(Easy)
思路:异或。a ^ a = 0,a ^ 0 = a。
def singleNumber(nums):
ans = 0
for num in nums: ans ^= num
return ans169 · 多数元素(Easy)
思路:Boyer-Moore 投票。
def majorityElement(nums):
candidate, count = None, 0
for num in nums:
if count == 0: candidate = num
count += 1 if num == candidate else -1
return candidate75 · 颜色分类(Medium)
思路:三指针(荷兰国旗)。
def sortColors(nums):
l, r = 0, len(nums) - 1
i = 0
while i <= r:
if nums[i] == 0:
nums[l], nums[i] = nums[i], nums[l]
l += 1; i += 1
elif nums[i] == 2:
nums[r], nums[i] = nums[i], nums[r]
r -= 1
else:
i += 131 · 下一个排列(Medium)
思路:从右找第一个降序转折点,交换。
287 · 寻找重复数(Medium)
思路:Floyd 判圈(把 nums 视为链表)。
def findDuplicate(nums):
slow = fast = 0
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast: break
slow = 0
while slow != fast:
slow = nums[slow]
fast = nums[fast]
return slow时间 O(n),空间 O(1)。
速查表
按 LeetCode 号索引
| # | 题名 | 难度 | 关键算法 | 时间 | 空间 |
|---|---|---|---|---|---|
| 1 | 两数之和 | Easy | 哈希 | O(n) | O(n) |
| 3 | 无重复最长子串 | Medium | 滑动窗口 | O(n) | O(字符集) |
| 4 | 两正序数组中位数 | Hard | 二分 | O(log(min(m,n))) | O(1) |
| 5 | 最长回文子串 | Medium | 中心扩展 | O(n²) | O(1) |
| 11 | 盛最多水的容器 | Medium | 双指针 | O(n) | O(1) |
| 15 | 三数之和 | Medium | 排序+双指针 | O(n²) | O(1) |
| 20 | 有效括号 | Easy | 栈 | O(n) | O(n) |
| 21 | 合并两个有序链表 | Easy | 双指针 | O(n+m) | O(1) |
| 22 | 括号生成 | Medium | 回溯 | O(4ⁿ/√n) | O(n) |
| 32 | 最长有效括号 | Hard | 栈 / DP | O(n) | O(n) |
| 33 | 搜索旋转数组 | Medium | 二分 | O(log n) | O(1) |
| 34 | 查找范围 | Medium | 二分 | O(log n) | O(1) |
| 42 | 接雨水 | Hard | 双指针 | O(n) | O(1) |
| 46 | 全排列 | Medium | 回溯 | O(n·n!) | O(n) |
| 53 | 最大子数组和 | Medium | DP / 贪心 | O(n) | O(1) |
| 55 | 跳跃游戏 | Medium | 贪心 | O(n) | O(1) |
| 56 | 合并区间 | Medium | 排序 | O(n log n) | O(1) |
| 62 | 不同路径 | Medium | DP | O(mn) | O(n) |
| 70 | 爬楼梯 | Easy | DP | O(n) | O(1) |
| 72 | 编辑距离 | Medium | DP | O(mn) | O(mn) |
| 75 | 颜色分类 | Medium | 三指针 | O(n) | O(1) |
| 76 | 最小覆盖子串 | Hard | 滑动窗口 | O( | s |
| 78 | 子集 | Medium | 回溯 | O(2ⁿ·n) | O(n) |
| 84 | 柱状图最大矩形 | Hard | 单调栈 | O(n) | O(n) |
| 94 | 中序遍历 | Easy | 栈 / 递归 | O(n) | O(n) |
| 101 | 对称二叉树 | Easy | DFS | O(n) | O(h) |
| 104 | 最大深度 | Easy | DFS | O(n) | O(h) |
| 121 | 买卖股票 I | Easy | 贪心 | O(n) | O(1) |
| 124 | 二叉树最大路径和 | Hard | DFS | O(n) | O(h) |
| 136 | 只出现一次数字 | Easy | 异或 | O(n) | O(1) |
| 139 | 单词拆分 | Medium | DP | O(n²·k) | O(n) |
| 141 | 环形链表 | Easy | 快慢指针 | O(n) | O(1) |
| 146 | LRU 缓存 | Medium | 哈希+双向链表 | O(1) | O(n) |
| 148 | 排序链表 | Hard | 归并 | O(n log n) | O(log n) |
| 152 | 乘积最大子数组 | Medium | DP | O(n) | O(1) |
| 169 | 多数元素 | Easy | 投票 | O(n) | O(1) |
| 198 | 打家劫舍 | Medium | DP | O(n) | O(1) |
| 200 | 岛屿数量 | Medium | DFS/BFS | O(mn) | O(mn) |
| 206 | 反转链表 | Easy | 指针 | O(n) | O(1) |
| 207 | 课程表 | Medium | 拓扑排序 | O(V+E) | O(V+E) |
| 215 | 第 K 大 | Medium | 堆 / 快选 | O(n) 平均 | O(k) |
| 234 | 回文链表 | Easy | 快慢+翻转 | O(n) | O(1) |
| 236 | 最近公共祖先 | Medium | DFS | O(n) | O(h) |
| 238 | 除自身以外数组的乘积 | Medium | 前缀积 | O(n) | O(1) |
| 239 | 滑动窗口最大值 | Hard | 单调队列 | O(n) | O(k) |
| 283 | 移动零 | Easy | 双指针 | O(n) | O(1) |
| 287 | 寻找重复数 | Medium | Floyd 判圈 | O(n) | O(1) |
| 295 | 数据流中位数 | Hard | 双堆 | O(log n) | O(n) |
| 300 | LIS | Medium | DP/贪心+二分 | O(n log n) | O(n) |
| 322 | 零钱兑换 | Medium | 完全背包 | O(an) | O(a) |
| 416 | 分割等和子集 | Medium | 0-1 背包 | O(n·sum) | O(sum) |
| 560 | 和为 K 的子数组 | Medium | 前缀和+哈希 | O(n) | O(n) |
| 739 | 每日温度 | Medium | 单调栈 | O(n) | O(n) |
| 1143 | LCS | Medium | DP | O(mn) | O(mn) |
按算法分类
| 算法 | 题号 |
|---|---|
| 哈希表 | 1, 49, 128, 560, 437 |
| 双指针 | 11, 15, 42, 75, 141, 142, 160, 283 |
| 滑动窗口 | 3, 76, 209, 239, 438 |
| 二分查找 | 4, 33, 34, 35, 74, 153, 300 |
| 排序 | 56, 75, 148, 215, 912 |
| 栈 | 20, 84, 155, 394, 739 |
| 堆 | 215, 295, 347, 23 |
| 回溯 | 17, 22, 39, 46, 51, 78, 79, 131 |
| 贪心 | 55, 121, 122, 123, 188, 309, 714, 763 |
| 树 | 94, 98, 101, 102, 104, 105, 108, 114, 199, 226, 230, 236, 437, 543, 124 |
| 图 | 200, 207, 210, 994, 208 |
| DP 一维 | 70, 72, 198, 279, 322, 416, 746 |
| DP 二维 | 5, 62, 64, 118, 152, 300, 1143 |
| DP 区间 | 5, 32, 312 |
| 位运算 | 136, 169, 287 |
刷题节奏建议
入门阶段(前 30 题)
按以下顺序,建立基础:
- 哈希:1, 49, 128
- 双指针:283, 11, 15
- 滑动窗口:3, 438
- 二叉树:104, 226, 101, 543
- 链表:206, 21, 160
- DP 入门:70, 198, 53
- 栈/堆:20, 155, 215
巩固阶段(30-70 题)
加入更复杂的题型:
- BFS/DFS:200, 994, 79
- 回溯:46, 78, 22
- 二分:35, 34, 33
- DP 进阶:300, 322, 416, 72
- 图论:207, 208
突破阶段(70-100 题)
攻克 Hard:
- 接雨水:42
- 滑动窗口最大值:239
- 最小覆盖子串:76
- 柱状图最大矩形:84
- LRU 缓存:146
- 合并 K 个链表:23
- 数据流中位数:295
- N 皇后:51
延伸阅读
- LeetCode Hot 100 — 官方题单
- 代码随想录 — 中文系统刷题教程
- 宫水三叶的题解 — 高质量题解
- labuladong 的算法小抄 — 模板总结
- 《剑指 Offer》— 面试专项
- 《编程之美》— 微软面试题集