Python LeetCode & ACM笔试 极简速查表
一、 核心容器:初始化、拼接与类型转换
1. 列表 (List) -> 动态数组 / 栈
- 创建与初始化:
- 空列表:
arr = [] - 定长初始化:
arr = [0] * n - 二维初始化:
dp = [[0] * n for _ in range(m)] (严禁使用 [[0]*n]*m) - 推导式:
arr = [x**2 for x in range(10) if x % 2 == 0]
- 拼接与扩展:
+ 运算符: [1, 2] + [3, 4] -> [1, 2, 3, 4] (产生新列表,耗时)- 原地扩展:
arr.extend([3, 4]) (等价于按顺序 append,$O(K)$)
- 操作:
append(x) $O(1)$, pop() $O(1)$, arr[::-1] 翻转 $O(N)$, sort() 原地排序 $O(N \log N)$。
2. 集合 (Set) -> 哈希去重
- 创建与初始化:
- 空集合:
s = set() (严禁使用 {},那是空字典) - 字面量:
s = {1, 2, 3}
- 拼接与运算:
- 并集:
s1 | s2 或 s1.union(s2) - 交集:
s1 & s2 或 s1.intersection(s2) - 差集:
s1 - s2
- 操作:
add(x) $O(1)$, remove(x) $O(1)$ (不存在会报错), discard(x) $O(1)$ (不存在不报错)。
3. 字典 (Dict) -> 哈希表
- 创建与初始化:
- 空字典:
d = {} 或 dict() - 默认字典:
d = defaultdict(int) (访问不存在的键返回默认值)(from collections import defaultdict) - 键值对:
d = {'a': 1, 'b': 2}
- 拼接与合并:
- Python 3.9+:
d3 = d1 | d2 (合并字典,同名键覆盖) - 原地更新:
d1.update(d2)
- 操作:
d.get(key, default) (安全读取), key in d $O(1)$。
4. 字符串 (String) -> 不可变字符序列
- 切片 (极其高频,极速操作):
s[:4] # “leet” (前 4 个字符)s[-4:] # “code” (后 4 个字符)s[::-1] # “edocteel” ($O(N)$ 极速反转)
- 查找与判断:
s.find("etc") # 返回 2 (子串起始索引,找不到返回 -1,笔试首选)s.startswith("le")# Trues.endswith("de") # Trues.isalnum() # True (判断是否只包含字母和数字,双指针判断回文时必用)s.isdigit() # False (判断是否纯数字)
- 替换与清理 (注意:必须重新赋值):
s2 = " a b c "s2.strip() # “a b c” (默认袪除首尾所有空白符)s2.replace(" ", "") # “abc” (极其好用的全量替换)
5. 元组 (Tuple) -> 不可变序列 / 可哈希键
- 创建与初始化:
- 空元组:
t = () - 单元素元组:
t = (1,) (⚠️ 必须带逗号,否则 (1) 会被当作整数运算) - 常规元组:
t = (1, 2, 3)
- 核心特性与用途:
- 可哈希 (Hashable): 因为绝对不可变,它是唯一能作为 Dict 键 (Key) 或存入 Set 的序列(List 绝对不行)。
- 多维状态记录: BFS 搜索时记录坐标
visited.add((r, c));DP 记忆化 memo[(idx, weight)] = res。
- 操作与解包:
- 极速解包:
r, c = (0, 1) (多变量同时赋值) - 索引与切片:
t[0], t[::-1] (与列表行为一致,但不能修改元素)
1
2
3
4
| # enumarate 同时获取索引和值
arr = ['a', 'b', 'c']
for i, val in enumerate(arr):
print(f"Index: {i}, Value: {val}")
|
6. 容器互相转换矩阵
| 转换方向 | 语法 | 说明 |
|---|
| List -> Set | set(arr) | 瞬间去重,常用于降维打击 $O(N)$ 查找 |
| Set -> List | list(s) | 集合转回数组,常用于去重后排序 |
| String -> List | list("abc") | 变为 ['a', 'b', 'c'],因为字符串不可变,需转列表修改 |
| List -> String | "".join(arr) | 高效拼接,arr 内部必须全是字符串元素 |
| Dict -> List | list(d.keys())
list(d.values()) | 分别提取字典的键或值构成列表 |
| List <-> Tuple | tuple(arr)
list(t) | 列表转元组以使其可哈希(存入 Set/Dict),或元组转列表以修改内容 |
二、 ACM 模式标准输入输出
处理国内笔试题(如牛客网)必备,避免 input() 导致超时或读取异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import sys
# 1. 万能读取法 (应对空格、换行混乱的输入)
# 将所有输入全部读入,打平为一个一维列表,后续通过迭代器或指针逐步提取
data = sys.stdin.read().split()
if not data: exit()
iterator = iter(data)
n = int(next(iterator)) # 提取第一个数
# 2. 单行读取 (极速版)
def get_ints():
return list(map(int, sys.stdin.readline().split()))
# 3. 矩阵读取 (读取 n 行 m 列)
n, m = get_ints()
matrix = [get_ints() for _ in range(n)]
# 4. 高效输出
# print 在大量输出时很慢,改用 sys.stdout.write
sys.stdout.write(" ".join(map(str, result_list)) + "\n")
|
三、 高频核心数据结构 (collections & heapq)
Python 循环结构极简速查
1. for 循环 (基于 range)
1
2
3
4
5
6
7
8
9
10
11
| # 基础遍历 [0, n-1]
for i in range(n): pass
# 区间遍历 [1, n-1]
for i in range(1, n): pass
# 倒序遍历 [n-1, 0] (左闭右开,步长-1)
for i in range(n - 1, -1, -1): pass
# 步长遍历 (每次+2)
for i in range(0, n, 2): pass
|
避坑:Python 中在 for 循环内修改 i 无效,会被下一轮自动重置。如需动态调整指针必须用 while。
2. 数据遍历 (无索引依赖)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 仅遍历值
for val in arr: pass
# 2. 同时获取索引和值 (高频)
for i, val in enumerate(arr): pass
# 3. 多序列并行遍历 (以最短为准)
for v1, v2 in zip(arr1, arr2): pass
# 4. 集合遍历 (无序)
for x in my_set: pass
# 5. 字典遍历
for k in my_dict: pass # 仅遍历键 (默认行为)
for v in my_dict.values(): pass # 仅遍历值
for k, v in my_dict.items(): pass # 同时遍历键值 (高频)
# 注意:严禁在遍历 Set/Dict 时修改其大小 (add/remove/pop),否则报错
|
3. while 循环
1
2
3
4
5
6
7
| # 1. 标准边界控制 (如二分查找)
while left <= right: pass
# 2. 容器判空 (代替 C++ 的 !q.empty())
# 空列表 []、空集合 set() 均等价于 False
while q:
node = q.popleft()
|
4. 控制流特有语法
1
2
3
4
5
6
7
8
9
10
11
12
| # for...else / while...else 结构 (省去 bool 标记)
for x in arr:
if x == target:
break # 触发 break,直接跳出,不执行 else
else:
# 循环自然跑完(未被 break 中断)时执行
return "Not Found"
# 基础控制符
continue # 跳过本次循环剩余代码,进入下一次
break # 强制跳出当前所在的一层循环
pass # 空语句占位符 (等价于 C++ 的 {})
|
5. 常用数据结构操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 双端队列 (BFS 核心)
from collections import deque
q = deque([1, 2])
q.append(3) # 尾部入队 O(1)
curr = q.popleft() # 头部出队 O(1)
# 2. 自动初始化字典 (图论建图、归类核心)
from collections import defaultdict
adj = defaultdict(list) # 默认值为 []
adj[0].append(1) # 免去 if 0 not in adj 的判断
# 3. 词频统计器
from collections import Counter
cnt = Counter("anagram") # {'a': 3, 'n': 1, 'g': 1, 'r': 1, 'm': 1}
# 4. 堆 / 优先队列 (Dijkstra、TopK 核心)
import heapq
arr = [3, 1, 2]
heapq.heapify(arr) # 原地建小顶堆 O(N)
heapq.heappush(arr, 0) # 插入 O(log N)
min_val = heapq.heappop(arr) # 弹出最小值 O(log N)
heapq.heappushpop(arr, 4) # 插入后弹出最小值 O(log N)
# 大顶堆技巧:压入 -x,弹出时再取负
|
四、 内置函数与语法糖
- 极值与数学:
float('inf'), float('-inf')。pow(a, b, mod) 快速幂取模算法(比自己手写快)。divmod(a, b) 同时返回商和余数。 - ASCII 转换:
ord('a') -> 97, chr(97) -> ‘a’。 - 字符串高频处理:
s.split() (默认处理所有连续空白符并丢弃空串)。s.isalnum() (判断是否全为字母或数字,回文串常用)。s.find(sub) (找子串,找不到返回 -1,严禁使用 index() 因为会抛异常报错)。
- 高阶遍历:
for i, val in enumerate(arr): (同时拿索引和值)。for x, y in zip(arr1, arr2): (双数组齐头并进)。
- 排序:
arr.sort() # 默认升序,原地排序,适用于数字/字符串arr.sort(reverse=True) # 降序排序arr.sort(key=lambda x: x[0]) # 按元素的第一个字段排序(如区间、元组、二维数组)arr.sort(key=lambda x: (x[0], -x[1])) # 多条件排序:优先按第一元素升序,相同则按第二元素降序sorted(arr) # 返回新排序后的列表,不改变原数组sorted(arr, key=..., reverse=...) # 一切排序技巧均可用- 例:
intervals.sort(key=lambda x: x[0]) # 区间按左端点排序,区间合并/覆盖问题高频
- 三元运算符(条件表达式):
- 语法:
a if 条件 else b (等价于 C/C++/Java 的 条件 ? a : b) - 示例:
res = x if x > 0 else -x # 取绝对值
五、 常用算法代码骨架
1. 二分查找 (标准库版)
自带的 bisect 库是 $O(\log N)$,直接替代手写二分。
1
2
3
4
5
6
| import bisect
arr = [1, 2, 2, 4]
# 找第一个 >= x 的位置 (左侧插入点)
idx_left = bisect.bisect_left(arr, 2) # 返回 1
# 找第一个 > x 的位置 (右侧插入点)
idx_right = bisect.bisect_right(arr, 2) # 返回 3
|
2. 图论 BFS 模板
1
2
3
4
5
6
7
8
9
10
11
12
13
| step = 0
visited = set([start])
q = deque([start])
while q:
size = len(q)
for _ in range(size): # 按层扩展
curr = q.popleft()
if curr == target: return step
for nxt in graph[curr]:
if nxt not in visited:
visited.add(nxt)
q.append(nxt)
step += 1
|
3. 并查集 (Union-Find)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| class UnionFind:
def __init__(self, size):
# 初始化:每个人的老总都是自己
self.parent = [i for i in range(size)]
# 记录以 i 为根的集合的大小,初始都是 1
self.size = [1] * size
# 连通分量的数量(有多少个独立的帮派)
self.count = size
def find(self, i):
# 路径压缩 (Path Compression)
if self.parent[i] != i:
self.parent[i] = self.find(self.parent[i])
return self.parent[i]
def union(self, i, j):
root_i = self.find(i)
root_j = self.find(j)
# 已经是同一个老总,无需合并
if root_i == root_j:
return False
# 按大小合并 (Union by Size):小树挂在大树下面
if self.size[root_i] < self.size[root_j]:
self.parent[root_i] = root_j
self.size[root_j] += self.size[root_i]
else:
self.parent[root_j] = root_i
self.size[root_i] += self.size[root_j]
# 合并成功,独立帮派数量减一
self.count -= 1
return True
def is_connected(self, i, j):
# 判断两人是否连通
return self.find(i) == self.find(j)
|
4. DPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| def dfs_template(node, state_var):
# 1. 递归终止条件 (Base Case)
# 遇到空节点、越界,或者满足特定结算条件时,立刻向上层返回
if not node:
return
if is_target_reached(node):
process_result(node, state_var)
return
# 2. 处理当前层逻辑 (Process Current Node)
# 根据题意,修改状态变量或记录当前节点的信息
update_state(state_var, node)
# 3. 递归下探 (Drill Down)
# 遍历所有可能的下一个节点(如树的左右子树,图的邻居节点)
# 注意:下探时必须传递更新后的状态变量
dfs_template(node.left, next_state(state_var))
dfs_template(node.right, next_state(state_var))
# 4. 恢复当前层状态 (Restore State / Backtrack) - 可选
# 如果 state_var 是全局变量或可变对象(如列表),在回溯时需要撤销第2步的修改
# 如果 state_var 是按值传递(如数字相加),则不需要此步
revert_state(state_var, node)
|
5. 01背包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| def zero_one_knapsack(weights, values, capacity):
n = len(weights)
# 1. 定义与初始化 DP 数组
# dp[j] 表示:当背包容量为 j 时,能获得的最大价值(或最小代价)
# 如果求最大值,初始化为 0;如果求最小值,初始化为 float('inf')
dp = [0] * (capacity + 1)
# base case: 容量为 0 时,价值为 0
dp[0] = 0
# 2. 外层循环:遍历每一个物品
for i in range(n):
w = weights[i]
v = values[i]
# 3. 内层循环:遍历背包容量(核心难点:必须倒序!)
# 倒序的原因:dp[j] 的更新依赖于上一层状态的 dp[j-w]。
# 如果正序遍历,dp[j-w] 会在当前物品这一层被提前更新,导致同一个物品被重复放入(这就变成了完全背包问题)。
for j in range(capacity, w - 1, -1):
# 4. 状态转移方程 (State Transition)
# 决策:是不放这个物品(保持 dp[j]),还是腾出 w 的空间放入这个物品(dp[j - w] + v)?
# 根据题意取 max 或 min
dp[j] = max(dp[j], dp[j - w] + v)
# 5. 返回最终状态
return dp[capacity]
|
树图什么的
现实校准:LeetCode 考什么,不考什么
在你听到“红黑树”、“B+树”等高大上的名词时,需要首先明确工业界底层开发与 LeetCode 算法应试的绝对分界线:
- 红黑树 (Red-Black Tree) / AVL 树: 它们是自平衡二叉搜索树,为了解决普通二叉搜索树退化成链表的问题而生。但在 LeetCode 面试中,几乎绝对不会让你从零手写一棵红黑树(代码量极大且极易出错,通常需要几百行严密的旋转逻辑)。如果你在题目中需要用到自平衡树的特性(即保持动态有序并支持 $O(\log N)$ 查找),在 C++ 中直接使用
std::set / std::map,在 Java 中使用 TreeMap,在 Python 中则依赖第三方库 sortedcontainers 或使用 bisect 模块结合列表模拟。 - LeetCode 的核心靶点: 是利用基础的二叉树、二叉搜索树 (BST)、字典树 (Trie) 以及基础图论(无向图/有向图的遍历、拓扑排序),来考察你的递归思维 (DFS) 和 层级思维 (BFS)。
以下是为你剥离冗余后,针对 LeetCode 场景的树与图 Cheat Sheet。
1. 二叉搜索树 (BST - Binary Search Tree)
辩证本质: 树形结构中的“二分查找”。它将线性数组的查找效率从 $O(N)$ 降维至 $O(\log N)$,但代价是每次插入/删除都需要动态维护其严格的大小关系。
- 核心物理规则: 对于任何一个节点,其左子树所有节点的值必小于该节点;其右子树所有节点的值必大于该节点。
- 最强推论(必考点): 对 BST 进行中序遍历 (Inorder Traversal:左-根-右),得到的必定是一个严格递增的有序数组。
- 经典题型: 验证二叉搜索树(98)、二叉搜索树中第K小的元素(230)、修剪二叉搜索树(669)。
Python 模板:利用中序遍历验证 BST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def isValidBST(root: TreeNode) -> bool:
# 辩证点:不要单纯只判断左孩子小于根、右孩子大于根。
# 必须传递一个不断收紧的上下界,确保整棵子树都符合规则。
def dfs(node, lower, upper):
if not node:
return True
if node.val <= lower or node.val >= upper:
return False
# 往左走,上限变成当前节点的值;往右走,下限变成当前节点的值
return dfs(node.left, lower, node.val) and dfs(node.right, node.val, upper)
return dfs(root, float('-inf'), float('inf'))
|
2. 字典树 / 前缀树 (Trie)
辩证本质: 典型的“空间换时间”。通过将字符串拆解为字符并构建多叉树,将海量字符串的匹配时间复杂度,从 $O(N \times L)$ 极致压缩到 $O(L)$($L$ 为单个单词的长度),代价是需要极其庞大的节点内存。
- 核心特征词: “前缀匹配”、“搜索联想”、“单词搜索”、“异或最大值(01字典树)”。
- 物理结构: 根节点为空,每个节点包含一个字典(或长度为 26 的数组)指向下一个字符,以及一个布尔标志位表示“是否为单词结尾”。
- 经典题型: 实现 Trie (前缀树)(208)、单词搜索 II(212 - Trie + DFS 的终极杀器)。
Python 模板:Trie 的标准实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class TrieNode:
def __init__(self):
self.children = {} # 存储子节点,例如 {'a': TrieNode, 'b': TrieNode}
self.is_end = False # 标识此处是否构成一个完整的单词
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end = True
def search(self, word: str) -> bool:
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end # 必须是单词结尾才算找到
|
3. 图 (Graph) 基础:无向图 vs 有向图
辩证本质: 树其实是图的一种极度受限的特例(树是没有环的连通无向图)。图抛弃了“父子关系”的严格层级,允许数据之间存在任意的网状联系。
- 无向图 (Undirected Graph): 边是双向的。A 认识 B,B 必然认识 A。
- 核心陷阱: 极易形成死循环死锁(如 A 走向 B,B 又走向 A)。
- 破解之道: 无论 DFS 还是 BFS,必须使用
visited 集合来记录走过的节点,坚决不走回头路。
- 有向图 (Directed Graph): 边是单向的。A 关注了 B,B 不一定关注 A。
4. 拓扑排序 (Topological Sort) - 针对有向无环图 (DAG)
辩证本质: 将一个存在依赖关系的网状图,拉平为一条线性的执行序列。如果图中存在环(如 A 依赖 B,B 依赖 A),则拓扑排序必然失败(发生死锁)。
- 核心特征词: “课程表”、“编译依赖”、“任务调度”、“是否存在先后矛盾”。
- 物理模型 (Kahn 算法):
- 统计所有节点的入度 (In-degree)(即有多少条边指向它,代表它被多少个前置条件卡着)。
- 将所有入度为 0 的节点(没有任何前置依赖的任务)放入队列。
- 弹出节点执行,并将其指向的所有邻居节点的入度减 1。
- 如果邻居入度降为 0,则加入队列。
- 经典题型: 课程表(207)、课程表 II(210)。
Python 模板:利用 BFS (Kahn算法) 实现拓扑排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| from collections import deque, defaultdict
def canFinish(numCourses: int, prerequisites: list[list[int]]) -> bool:
# 1. 初始化邻接表和入度数组
graph = defaultdict(list)
in_degree = [0] * numCourses
# 2. 构建图 (prerequisites[i] = [a, b] 表示选 a 必须先学 b,即 b -> a)
for course, pre in prerequisites:
graph[pre].append(course)
in_degree[course] += 1
# 3. 将所有入度为 0 的节点(即不需要任何先修课的课程)入队
queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
# 4. 记录已完成的课程数量
completed_count = 0
while queue:
node = queue.popleft()
completed_count += 1
# 将依赖该课程的所有后续课程入度减 1
for neighbor in graph[node]:
in_degree[neighbor] -= 1
# 一旦前置依赖全部解除,加入队列准备学习
if in_degree[neighbor] == 0:
queue.append(neighbor)
# 辩证点:如果完成的数量等于总课程数,说明不存在环,可以学完。
# 如果最后还有课程没学完(入度死活降不到0),说明图里必定存在互相依赖的“死锁环”。
return completed_count == numCourses
|
六、 根据数据范围推断时间复杂度 (防超时指南)
机试时,先看题目给定的变量范围 $N$,直接反推该用什么算法:
- $N \le 20$: 回溯法、状态压缩 DP $\rightarrow O(2^N)$ 或 $O(N!)$
- $N \le 400$: 三重循环、Floyd 算法 $\rightarrow O(N^3)$
- $N \le 2000$: 两重循环、普通 DP $\rightarrow O(N^2)$
- $N \le 10^5$: 排序、堆、二分查找、线段树 $\rightarrow O(N \log N)$
- $N \le 10^6$: 双指针、滑动窗口、单调栈、哈希表 $\rightarrow O(N)$
- $N \ge 10^9$: 数学规律、快速幂、二分答案 $\rightarrow O(\log N)$ 或 $O(1)$
七、Leetcode Python 数据结构速成
已完成联网核对。前文提到的“有效的字母异位词”题号有误(LeetCode 24 实际为“两两交换链表中的节点”,242 才是“有效的字母异位词”),下表已修正。
这是一份纯粹用于检验 Python 数据结构熟练度的闭卷通关表:
| 题目号及名称 | 核心知识点 | 闭卷 AC 验收标准 |
|---|
| 125. 验证回文串 | 字符串过滤、切片翻转 | 熟练使用推导式 [c.lower() for c in s if c.isalnum()],用切片 s == s[::-1] 一行判断,不写双指针。 |
| 349. 两个数组的交集 | Set 的初始化与交集运算 | 严禁手写两层循环,必须一句话搞定:return list(set(nums1) & set(nums2))。 |
| 128. 最长连续序列 | Set 的 $O(1)$ 极速查找 | 将数组转为 set,彻底理解并利用 num in num_set 的 $O(1)$ 特性防超时。 |
| 242. 有效的字母异位词 | 词频统计 (Counter) | 严禁手写循环统计,直接引入 Counter,使用 return Counter(s) == Counter(t) 一行秒杀。 |
| 49. 字母异位词分组 | defaultdict、Tuple 作为哈希键 | 熟练写出 d = defaultdict(list),深刻理解必须将排序后的字符串转为 tuple 才能作为字典的 key。 |
| 169. 多数元素 | 字典遍历 / API 获取极值 | 熟练手写字典遍历寻找最大值,或直接使用 Counter(nums).most_common(1)[0][0]。 |
| 20. 有效的括号 | List 作为栈的使用 | 熟练使用 append() 和 pop(),并用静态字典 {')': '(', ']': '[', '}': '{'} 优雅映射替代大量 if-else。 |
| 102. 二叉树的层序遍历 | deque 实现 BFS | 倒背如流 q = deque([root]) 和 q.popleft(),严禁在此处使用 list.pop(0)。 |
| 200. 岛屿数量 | 多维坐标的 Tuple 打包与 Set 去重 | 在二维 BFS/DFS 中,极其熟练地将坐标打包成元组存入访问记录:visited.add((r, c))。 |
| 215. 数组中的第K个最大元素 | heapq 核心 API | 熟练使用 heapq.heapify 以及维护大小为 $K$ 的小顶堆(heappush 和 heappop)。 |
| 347. 前 K 个高频元素 | 大顶堆技巧与复杂结构嵌套 | 结合 Counter 统计频率,并熟练写出“取负数塞入小顶堆模拟大顶堆”的操作:heappush(hp, (-freq, num))。 |
| 56. 合并区间 | 列表的高阶排序 (lambda) | 不看资料直接写出按左端点升序排列的定制规则:intervals.sort(key=lambda x: x[0])。 |