跳到主要内容

Python数据结构速查

本文档汇总Python中常用的数据结构及其方法,便于快速查阅。


复杂度速查总览

操作listdict / setdequeheapq
随机访问 a[i]O(1)O(n)O(1) 仅堆顶
末尾增删O(1) 均摊O(1)O(log n)
头部增删O(n)O(1)
中间插入/删除O(n)O(n)
val in 容器O(n) 线性扫描O(1) 均摊O(n)
排序 .sort()O(n log n)O(n log n)
原地堆化 heapifyO(n)
二分查找 bisectO(log n)
构建 Counter/dictO(n)

核心结论:判断"某元素是否存在",用 set / dict(O(1))而非 list(O(n))。


字符串 str

底层实现

CPython 中字符串是不可变的 Unicode 字符序列,根据字符范围自动选择存储方式:

  • Latin-1 范围(≤ U+00FF)→ 每字符 1 字节
  • BMP 范围(≤ U+FFFF)→ 每字符 2 字节
  • 全 Unicode → 每字符 4 字节

不可变意味着任何修改操作都会创建新字符串,因此循环拼接字符串要用 ''.join(list) 而非 +=(后者是 O(n²))。

# 为什么 join 比 += 快?
# s += x 每次都要申请新内存、复制旧内容 → 累计 O(n²)
# ''.join(parts) 先算总长度、一次申请、一次写入 → O(n)
操作时间复杂度说明
s[i] 索引O(1)直接偏移计算
s[a:b] 切片O(k) k=切片长度创建新字符串
s1 + s2 拼接O(n+m)创建新字符串
sub in sO(n·m) 最坏CPython用优化过的Boyer-Moore-Horspool
s.find/indexO(n·m) 最坏同上
s.split/joinO(n)线性扫描
s.replaceO(n)线性扫描 + 新建

常用方法

# 格式化与转换
format(*args, **kwargs) # 格式化字符串,算法中可用于进制转换
split(sep=None, maxsplit=-1) # 以sep分割字符串,不指定则按空白符分割
splitlines([keepends=False]) # 按行分割,返回行列表(不包含
),keepends=True则保留

strip([chars]) # 去除首尾字符,默认去除 \r, \n, " "
lstrip([chars]) # 只去除左侧
rstrip([chars]) # 只去除右侧
join(iterable) # 拼接字符串,如 ','.join(['leet', 'code']) => "leet,code"
replace(old, new[, count]) # 字符串替换

# 查询与判断
count(sub[, start[, end]]) # 统计子串出现次数
startswith(prefix) # 是否以prefix开头
endswith(suffix) # 是否以suffix结尾
find(sub[, start[, end]]) # 查找子串位置,找不到返回 -1
index(sub[, start[, end]]) # 查找子串位置,找不到抛出 ValueError
rfind(sub) # 从右向左查找
rindex(sub) # 从右向左查找,找不到抛出异常

# 类型判断
isdigit() # 是否全为数字字符
isalpha() # 是否全为字母
isalnum() # 是否全为字母或数字
isspace() # 是否全为空白字符
isupper() # 是否全为大写字母
islower() # 是否全为小写字母

# 大小写转换
upper() # 全部大写
lower() # 全部小写
capitalize() # 首字母大写
title() # 每个单词首字母大写
swapcase() # 大小写互换

# 对齐填充
center(width[, fillchar]) # 居中对齐
ljust(width[, fillchar]) # 左对齐(右侧填充)
rjust(width[, fillchar]) # 右对齐(左侧填充)
zfill(width) # 左侧填充 '0'

# 字符编码
ord(c) # 字符转ASCII码,如 ord('a') => 97
chr(i) # ASCII码转字符,如 chr(97) => 'a'

算法应用

# 进制转换(LeetCode 191. Number of 1 Bits)
bin(n) # 转为二进制字符串,如 bin(10) => '0b1010'
bin(n).count('1') # 统计二进制中1的个数
oct(n) # 转为八进制字符串
hex(n) # 转为十六进制字符串
format(n, 'b') # 转为二进制字符串(无前缀)
format(n, '08b') # 转为8位二进制字符串(不足补0)
format(n, 'o') # 转为八进制字符串(无前缀)
format(n, 'x') # 转为十六进制字符串(无前缀)

# 字符串与数字互转
num = int("123") # 字符串转整数
num = int("ff", 16) # 十六进制字符串转整数
num = int("1010", 2) # 二进制字符串转整数
s = str(123) # 整数转字符串

# 字符处理技巧
chars = list("hello") # 字符串转字符列表
s = ''.join(chars) # 字符列表转字符串
s[::-1] # 反转字符串
sorted(s) # 返回排序后的字符列表(不改变原字符串)

# eval:将字符串作为表达式计算(谨慎使用)
eval("1 + 2") # => 3
eval("'hello'.upper()") # => 'HELLO'

列表 list

底层实现

CPython 中 list 是动态数组(array of pointers),存储的是指向 PyObject 的指针数组,而非元素本身。

扩容策略(不是翻倍,而是 ~1.125 倍):

新容量 = 旧容量 + (旧容量 >> 3) + (旧容量 < 9 ? 3 : 6)
# 即:0→4→8→16→25→35→46→58→72→88...

均摊下来 append 是 O(1)。每次扩容需复制所有指针,代价是 O(n),但触发频率低。

val in lst 的实现:逐一比较每个元素(先比地址,相同则返回 True;否则调用 __eq__),最坏 O(n),找不到时必须扫完全部。

# 演示:list 的 in 是 O(n) 线性扫描
lst = list(range(10_000_000))
val = 9_999_999

# 慢:O(n)
val in lst # ~100ms

# 快:O(1),先转 set
s = set(lst) # O(n) 一次性构建
val in s # ~50ns
操作时间复杂度说明
lst[i]O(1)指针偏移,极快
lst.append(x)O(1) 均摊偶尔触发 O(n) 扩容
lst.pop()O(1)尾部,无需移动
lst.insert(0, x)O(n)所有元素右移
lst.pop(0)O(n)所有元素左移
lst.remove(x)O(n)先查找 O(n),再移动 O(n)
x in lstO(n)线性扫描,无哈希
lst.sort()O(n log n)Timsort,稳定排序
lst.reverse()O(n)原地翻转
lst[a:b]O(k)k=切片长
lst.count(x)O(n)全量扫描
lst.index(x)O(n)找到即返回

Timsort:Python 的排序算法,结合归并排序与插入排序,对部分有序数据特别高效(接近 O(n)),最坏 O(n log n),稳定(相等元素不改变相对顺序)。

常用方法

# 排序与反转
lst.sort(key=None, reverse=False) # 原地排序(稳定排序)
sorted(lst, key=None, reverse=False) # 返回新列表,不改变原列表
lst.reverse() # 原地反转
lst[::-1] # 切片反转,返回新列表
reversed(lst) # 返回反向迭代器,不创建新列表(节省内存)

# 增删改查
lst.append(val) # 末尾添加元素,O(1)
lst.extend(t) # 批量添加,等价于 lst += t
lst.insert(i, val) # 在位置i插入元素,O(n)
lst.pop() # 移除并返回最后一个元素,O(1)
lst.pop(i) # 移除并返回位置i的元素,O(n)
lst.remove(val) # 移除第一个值为val的元素,O(n)
lst.clear() # 清空列表

# 查询
lst.count(val) # 统计val出现次数
lst.index(val[, start[, end]]) # 查找val的第一个索引,不存在抛出 ValueError,可指定搜索范围
val in lst # 判断是否存在,O(n)

# 其他
lst.copy() # 浅拷贝
len(lst) # 列表长度
sum(lst) # 求和
min(lst) # 最小值
max(lst) # 最大值

列表推导式

# 基本用法
squares = [x**2 for x in range(10)]

# 带条件
evens = [x for x in range(10) if x % 2 == 0]

# 嵌套(二维展平)
flat = [x for row in matrix for x in row]

# 二维列表初始化(注意浅拷贝问题)
matrix = [[0] * n for _ in range(m)] # 正确:每行独立
# matrix = [[0] * n] * m # 错误!所有行引用同一对象

# 带索引遍历(enumerate)
for i, val in enumerate(lst):
print(i, val)

# 同时遍历两个列表(zip)
for a, b in zip(lst1, lst2):
print(a, b)

切片操作

# --- 读取(返回新列表,原列表不变)---
lst[start:end] # 左闭右开 [start, end),负索引从尾部数
lst[start:end:step] # 带步长,step 可为负数
lst[:] # 全部元素(等价于浅拷贝)
lst[::-1] # 整体反转
lst[::2] # 偶数索引元素(0,2,4,...)
lst[1::2] # 奇数索引元素(1,3,5,...)
lst[:k] # 前 k 个元素
lst[-k:] # 后 k 个元素
lst[:-k] # 去掉最后 k 个元素

# --- 写入(切片赋值,原地修改)---
lst[1:3] = [10, 20] # 替换区间(长度可以不等!)
lst[1:3] = [] # 删除区间(等价于 del lst[1:3])
lst[1:1] = [10, 20] # 在 index=1 处插入(不删除原元素)
lst[::2] = [0] * len(lst[::2]) # 将所有偶数索引元素置 0

# --- 常见 coding 技巧 ---
a, b = lst[:n], lst[n:] # 将列表分成两段
lst[l:r+1] = lst[l:r+1][::-1] # 反转子数组 [l, r](LeetCode 原地反转技巧)

# 示例:旋转数组右移 k 位(LeetCode 189)
def rotate(nums, k):
k %= len(nums)
nums[:] = nums[-k:] + nums[:-k] # nums[:] 是原地修改(不新建列表引用)
# 不能写 nums = nums[-k:] + nums[:-k],那样只是改了局部变量

拷贝:浅拷贝 vs 深拷贝

# --- 三种等价的浅拷贝 ---
b = lst.copy() # 列表方法,语义最清晰
b = lst[:] # 切片语法,最简洁,常见于 coding
b = list(lst) # 构造函数,适用于任意可迭代对象

# 浅拷贝只复制"第一层",嵌套列表仍共享引用
a = [[1, 2], [3, 4]]
b = a.copy() # b 是新列表,但 b[0] 和 a[0] 指向同一个 [1, 2]
b[0].append(99)
print(a) # [[1, 2, 99], [3, 4]] ← a 也变了!
b[1] = [99]
print(a) # [[1, 2, 99], [3, 4]] ← a 不变(替换整行不影响 a)

# --- 深拷贝:完全独立 ---
import copy
b = copy.deepcopy(a) # 递归复制所有层,b 与 a 完全独立

# --- coding 中最常见的坑 ---
path = []
def dfs():
ans.append(path) # 错误!path 是引用,后续修改会影响 ans 中的结果
ans.append(path[:]) # 正确:每次记录当前状态的副本
ans.append(list(path)) # 等价写法

何时用深拷贝? 只有嵌套结构(列表的列表、列表的字典等)且需要完全独立时才用 deepcopy,代价较高(O(n) 且常数大)。大多数算法题用 path[:] 即可。

算法常用技巧

# --- 初始化 ---
lst = [0] * n # 全 0 列表
lst = [float('inf')] * n # 全无穷(初始化 dp/距离数组)
lst = list(range(n)) # [0, 1, 2, ..., n-1]
matrix = [[0]*cols for _ in range(rows)] # 二维数组(每行独立!)

# --- 解包 ---
a, b, c = [1, 2, 3] # 直接解包
first, *rest = lst # 头部 + 剩余
*init, last = lst # 剩余 + 尾部
a, *mid, b = lst # 首尾 + 中间
merged = [*lst1, *lst2] # 合并列表(等价于 lst1 + lst2)
a, b = b, a # 交换两变量(Python 特有,无需 tmp)

# --- 输入处理(ACM 模式)---
n = int(input())
lst = list(map(int, input().split())) # 读一行整数
matrix = [list(map(int, input().split())) for _ in range(n)] # 读矩阵

# --- 矩阵操作 ---
# 转置矩阵(行列互换)
transposed = list(map(list, zip(*matrix)))
# 顺时针旋转 90°
rotated = list(map(list, zip(*matrix[::-1])))
# 逆时针旋转 90°
rotated = list(map(list, zip(*matrix)))[::-1]

# --- 排序技巧 ---
lst.sort(key=lambda x: x[1]) # 按第二元素升序
lst.sort(key=lambda x: (-x[1], x[0])) # 按第二元素降序,再按第一升序
lst.sort(key=lambda x: (len(x), x)) # 按长度,再按字典序
words.sort(key=lambda w: [ord(c)-ord('a') for c in w]) # 自定义字母顺序

import functools
lst.sort(key=functools.cmp_to_key(lambda a, b: a - b)) # 自定义比较函数

# --- 常用聚合 ---
total = sum(lst)
total = sum(x**2 for x in lst) # 生成器表达式,不创建中间列表
max_val = max(lst)
max_val = max(lst, key=lambda x: x[1]) # 按第二元素找最大
max_val = max(lst, default=0) # 空列表时返回 default(Python 3.4+)
flat_sum = sum(sum(row) for row in matrix) # 矩阵求和

# --- 去重与统计 ---
unique = list(set(lst)) # 去重(不保序)
unique_sorted = sorted(set(lst)) # 去重并排序
freq = {}
for x in lst: freq[x] = freq.get(x, 0) + 1 # 手动计数

# --- 双指针/滑动窗口常用 ---
left, right = 0, len(lst) - 1 # 对撞指针初始化
while left < right:
# ... 双指针逻辑 ...
left += 1; right -= 1

# --- 前缀和(一维)---
n = len(lst)
prefix = [0] * (n + 1)
for i, x in enumerate(lst):
prefix[i+1] = prefix[i] + x
# 区间和 [l, r](闭区间)
range_sum = prefix[r+1] - prefix[l]

# --- 差分数组 ---
diff = [0] * (n + 1)
# 区间 [l, r] 加 val:
diff[l] += val; diff[r+1] -= val
# 还原:
from itertools import accumulate
result = list(accumulate(diff))[:n]

字典 dict

底层实现

CPython 3.6+ 的 dict 是紧凑哈希表(compact hash table),由两部分组成:

  • 索引数组(稀疏):存储哈希槽,记录"键值对在 entries 数组中的位置"
  • entries 数组(紧凑):按插入顺序存储 (hash, key, value) 三元组

这使得 dict 在 Python 3.7+ 保证插入顺序,且内存比旧版紧凑约 20-25%。

哈希流程

1. 计算 hash(key) → 整数
2. hash & (capacity-1) → 初始槽位 i(capacity 总是 2 的幂)
3. 若槽位空 → 插入;若 hash 和 key 均相等 → 更新;否则 → 探测下一槽
4. 探测方式:i = (5*i + 1 + perturb) % capacity (伪随机探测,非线性探测)

负载因子:默认 2/3,超过后触发扩容(容量翻倍),重新哈希所有键。

哈希冲突:最坏情况(所有键哈希相同)退化为 O(n),实际中极少发生。

# 为什么 key in dict 是 O(1)?
# 1. hash(key) 计算出槽位,直接跳转 → 不需要遍历
# 2. 只有极少数冲突需要探测 1-2 次
d = {i: i for i in range(10_000_000)}
999999 in d # ~50ns,和 n=10 时几乎一样快
操作时间复杂度说明
d[key] / d.get(key)O(1) 均摊哈希查找
d[key] = valO(1) 均摊哈希插入/更新
del d[key]O(1) 均摊哈希删除
key in dO(1) 均摊哈希查找
d.keys() / .values() / .items()O(1)返回视图,不复制
遍历 for k in dO(n)遍历 entries 数组
d.update(other)O(m) m=other大小逐个插入
构建 dict(pairs)O(n)逐个哈希插入

为什么自定义类的对象默认可哈希? Python 对象默认用 id()(内存地址)作为哈希值,所以任意对象都可以作为 dict 的 key。但如果定义了 __eq__,必须同时定义 __hash__(否则变为不可哈希)。

常用方法

# 访问与修改
d[key] # 访问键值,不存在抛出 KeyError
d.get(key, default=None) # 安全访问,不存在返回default
d.setdefault(key, default) # 如key不存在则设置默认值并返回
d.update(other) # 批量更新(可传字典或键值对)

# 删除
d.pop(key[, default]) # 删除并返回指定键值,不存在时返回default
d.popitem() # 删除并返回最后一个键值对(Python 3.7+ LIFO顺序)
del d[key] # 删除指定键,不存在抛出 KeyError
d.clear() # 清空字典

# 视图对象(动态,随字典变化而更新)
d.keys() # 返回键的视图
d.values() # 返回值的视图
d.items() # 返回键值对的视图

# 构造技巧
dict.fromkeys(iterable, value=None) # 用可迭代对象创建字典
{k: v for k, v in pairs} # 字典推导式

迭代技巧

# 遍历键值对
for k, v in d.items():
print(k, v)

# 按键排序遍历
for k in sorted(d):
print(k, d[k])

# 按值排序遍历(升序)
for k, v in sorted(d.items(), key=lambda x: x[1]):
print(k, v)

# 按值排序遍历(降序)
for k, v in sorted(d.items(), key=lambda x: -x[1]):
print(k, v)

# 字典合并(Python 3.9+)
merged = d1 | d2 # 返回新字典
d1 |= d2 # 原地合并

# 判断键是否存在
key in d # O(1)
key not in d # O(1)

集合 set

底层实现

set 和 dict 共用同一套哈希表机制,区别是 set 只存 key,不存 value

  • 底层:稀疏哈希表,槽位只存 (hash, key)
  • 负载因子:2/3,超过后扩容
  • val in set:计算 hash(val) → 直接定位槽位 → O(1)
# list vs set 的 in 性能对比(10M 元素)
import time
data = list(range(10_000_000))
s = set(data)
target = 9_999_999

# list: O(n),约 100ms
t0 = time.time(); _ = target in data; print(time.time()-t0)

# set: O(1),约 50ns
t0 = time.time(); _ = target in s; print(time.time()-t0)
操作时间复杂度说明
s.add(x)O(1) 均摊哈希插入
x in sO(1) 均摊哈希查找
s.remove(x) / s.discard(x)O(1) 均摊哈希删除
s1 | s2 并集O(n+m)遍历两个集合
s1 & s2 交集O(min(n,m))遍历较小集合,在较大集合中查找
s1 - s2 差集O(n)遍历 s1
s1 ^ s2 对称差O(n+m)
set(lst) 构建O(n)逐个插入

为什么 set 的交集是 O(min(n,m)) 而非 O(n+m)? CPython 会自动选择遍历较小的集合,对每个元素在较大集合中做 O(1) 哈希查找。

常用方法

# 创建
s = {1, 2, 3}
s = set([1, 2, 3]) # 从可迭代对象创建(可去重)
s = set() # 注意:{} 是空字典而非空集合

# 增删
s.add(val) # 添加元素,已存在则无效
s.update(*others) # 迭代添加多个元素/集合
s.remove(val) # 删除元素,不存在则抛出 KeyError
s.discard(val) # 删除元素,不存在则不报错(推荐)
s.pop() # 随机移除并返回一个元素
s.clear() # 清空集合

# 集合运算
s1 | s2 # 并集(union)
s1 & s2 # 交集(intersection)
s1 - s2 # 差集(difference),属于s1但不属于s2
s1 ^ s2 # 对称差集(symmetric_difference),只属于一个集合的元素
s1.union(s2) # 并集(等价于 |)
s1.intersection(s2) # 交集(等价于 &)
s1.difference(s2) # 差集(等价于 -)

# 关系判断
val in s # O(1) 查询
s1.issubset(s2) # s1是否是s2的子集(s1 <= s2)
s1.issuperset(s2) # s1是否是s2的超集(s1 >= s2)
s1.isdisjoint(s2) # 两集合是否无交集

去重与快速查找

# 列表去重(不保序,最简洁)
unique = list(set(lst))

# 列表去重(保持原顺序)
seen = set()
unique = [x for x in lst if not (x in seen or seen.add(x))]

# 利用集合快速查找:O(n)构建,O(1)查询
lookup = set(lst)
if target in lookup:
print("found")

# 两数之和经典用法
def twoSum(nums, target):
seen = {}
for i, x in enumerate(nums):
if target - x in seen:
return [seen[target - x], i]
seen[x] = i

collections 模块

Python内置的数据结构扩展库,提供高效的专用数据结构。

数据结构核心作用典型场景
Counter计数器:快速统计可迭代对象中元素出现次数词频统计、热点Key计数、字母异位词
defaultdict字典扩展:访问不存在的Key自动初始化默认值计数统计、分组聚合、嵌套字典
OrderedDict有序字典:保证字典插入顺序(Python 3.7+ 普通dict也有序,但此类提供额外API)LRU缓存、需要移动键到首尾的场景
deque双端队列:两端增删 O(1),优于列表首尾操作 O(n)队列/栈、滑动窗口、BFS
namedtuple命名元组:给元组元素命名,兼具不可变性和可读性轻量数据对象(坐标、用户信息)
ChainMap链式映射:合并多个字典,查询时按顺序遍历(无需拷贝)多配置合并、环境变量优先级覆盖

Counter 计数器

Counter 继承自 dict,底层就是哈希表。构建 Counter(iterable) 是 O(n),most_common(k) 是 O(n log k)(内部用堆)。

from collections import Counter

# 创建
cnt = Counter([1, 1, 2, 3, 3, 3]) # Counter({3: 3, 1: 2, 2: 1})
cnt = Counter('abracadabra') # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
cnt = Counter({'a': 3, 'b': 1}) # 直接从字典创建

# 常用操作
cnt[key] # 访问计数,不存在返回0(不是KeyError)
cnt.most_common(n) # 返回出现次数最多的n个元素(列表,降序)
cnt.most_common() # 不指定n则返回所有(按频率降序)
cnt.elements() # 返回所有元素的迭代器(按计数重复)
cnt.total() # 所有计数总和(Python 3.10+)
cnt.subtract(iterable) # 减少计数(计数可变为负数)
cnt.update(iterable) # 增加计数

# 遍历操作(继承自 dict)
cnt.keys() # 返回所有元素的视图(去重后的键)
cnt.values() # 返回所有计数的视图
cnt.items() # 返回 (元素, 计数) 键值对视图
for elem, count in cnt.items(): # 遍历每个元素及其计数
print(f"{elem}: {count}")

# 算术运算
c1 + c2 # 计数相加(只保留正数)
c1 - c2 # 计数相减(只保留正数)
c1 & c2 # 取每个键的最小计数
c1 | c2 # 取每个键的最大计数

# 示例:统计字母频率
s = "abracadabra"
cnt = Counter(s)
print(cnt.most_common(3)) # [('a', 5), ('b', 2), ('r', 2)]

# 示例:判断字母异位词(LeetCode 438)
def isAnagram(s, t):
return Counter(s) == Counter(t)

defaultdict 默认字典

defaultdict 继承自 dict,复杂度与 dict 完全相同(O(1) 增删查)。唯一区别是重写了 __missing__:访问不存在的 key 时自动调用工厂函数创建默认值。

from collections import defaultdict

# 创建(传入无参可调用对象作为工厂函数)
dd = defaultdict(list) # 默认值为空列表 []
dd = defaultdict(int) # 默认值为 0
dd = defaultdict(set) # 默认值为空集合 set()
dd = defaultdict(lambda: -1) # 默认值为 -1

# 核心特性:访问不存在的键时,自动创建默认值(不抛 KeyError)
dd['new_key'].append(1) # 自动创建空列表并追加
dd['count'] += 1 # 自动创建0并加1

# 注意区别
dd[key] # 若key不存在,触发默认值,key被插入字典
dd.get(key) # 若key不存在,返回None,key不被插入(不触发默认值)

# 示例:分组
pairs = [('a', 1), ('b', 2), ('a', 3)]
groups = defaultdict(list)
for key, val in pairs:
groups[key].append(val)
# groups: {'a': [1, 3], 'b': [2]}

# 示例:计数(等价于Counter)
freq = defaultdict(int)
for c in "abracadabra":
freq[c] += 1

OrderedDict 有序字典

from collections import OrderedDict

# 创建
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3

# 独有操作(普通dict没有)
od.move_to_end('a') # 将键'a'移动到末尾
od.move_to_end('c', last=False) # 将键'c'移动到开头
od.popitem(last=True) # 弹出最后一个键值对(LIFO)
od.popitem(last=False) # 弹出第一个键值对(FIFO)

# 示例:LRU缓存(最近最少使用)
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.cap = capacity

def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key) # 访问后移至末尾(最近使用)
return self.cache[key]

def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.cap:
self.cache.popitem(last=False) # 淘汰最久未使用的(头部)

deque 双端队列

底层实现

deque 不是单个数组,而是双向链表连接的固定大小块(block)数组

  • 每个 block 存储 64 个元素(CPython 实现)
  • 维护 leftblock/rightblock 指针和 leftindex/rightindex 偏移
  • 两端 append/pop:直接移动指针 → 严格 O(1)(不是均摊)
  • 中间 insert(i, x) / 随机访问 dq[i]:需遍历 block 链 → O(n)
# list 与 deque 头部操作对比(10万次 appendleft)
# list.insert(0, x):每次把所有元素右移 → O(n²) 总
# deque.appendleft(x):只移动指针 → O(n) 总

list: ~500ms(100万次 insert(0,x))
deque: ~10ms (100万次 appendleft(x))
操作时间复杂度vs list
dq.append(x)O(1)list O(1) 均摊
dq.appendleft(x)O(1)list O(n)
dq.pop()O(1)list O(1)
dq.popleft()O(1)list O(n)
dq[i] 随机访问O(n)list O(1)
x in dqO(n)list O(n)
dq.rotate(k)O(k)

选 list 还是 deque? 只在尾部操作 → list(随机访问更快) 需要头部操作(BFS队列、滑动窗口)→ deque

from collections import deque

# 创建
dq = deque()
dq = deque([1, 2, 3])
dq = deque(maxlen=3) # 设置最大长度,满了追加时自动弹出对侧元素

# 操作
dq.append(x) # 右侧添加,O(1)
dq.appendleft(x) # 左侧添加,O(1)
dq.pop() # 右侧弹出,O(1)
dq.popleft() # 左侧弹出,O(1)
dq.extend(iterable) # 右侧批量添加
dq.extendleft(iterable) # 左侧批量添加(注意顺序会反转)
dq.rotate(n) # 正数:右旋(尾部移到头部),负数:左旋
dq.clear() # 清空
dq.insert(i, x) # 在位置i插入元素,O(n)(不常用,需要时用list)
dq.count(val) # 统计val出现次数
dq.remove(val) # 删除第一个值为val的元素
dq.reverse() # 原地反转
len(dq) # 长度
dq[0] # 查看头部(不弹出)
dq[-1] # 查看尾部(不弹出)

# 可用作栈(右进右出)和队列(右进左出)

# 示例:BFS层序遍历
from collections import deque
q = deque([root])
while q:
node = q.popleft()
if node.left: q.append(node.left)
if node.right: q.append(node.right)

# 示例:滑动窗口最大值(单调递减队列)
def maxSlidingWindow(nums, k):
dq = deque() # 存索引,保持单调递减
res = []
for i, x in enumerate(nums):
while dq and nums[dq[-1]] <= x:
dq.pop()
dq.append(i)
if dq[0] <= i - k: # 队头超出窗口范围
dq.popleft()
if i >= k - 1:
res.append(nums[dq[0]])
return res

namedtuple 命名元组

from collections import namedtuple

# 创建命名元组类型
Point = namedtuple('Point', ['x', 'y'])
Person = namedtuple('Person', 'name age score') # 也可以用空格分隔的字符串

# 使用
p = Point(3, 4)
print(p.x, p.y) # 3 4(可用属性名访问)
print(p[0], p[1]) # 3 4(也可以用索引访问)
print(p) # Point(x=3, y=4)

# 不可变(元组特性)
# p.x = 5 # 报错 AttributeError

# 实用方法
p._asdict() # 转为 OrderedDict,如 {'x': 3, 'y': 4}
p._replace(x=10) # 返回新元组,将x替换为10
Point._fields # 返回字段名元组 ('x', 'y')

# 示例:表示坐标
directions = [Point(0, 1), Point(0, -1), Point(1, 0), Point(-1, 0)]
for d in directions:
new_x, new_y = cur.x + d.x, cur.y + d.y

ChainMap 链式映射

from collections import ChainMap

# 创建(多个字典按顺序查找,无需合并拷贝)
defaults = {'color': 'red', 'size': 'M'}
overrides = {'color': 'blue', 'weight': 1.5}
combined = ChainMap(overrides, defaults)

# 查找:按顺序查找,找到第一个匹配即返回
print(combined['color']) # 'blue'(overrides中找到)
print(combined['size']) # 'M'(overrides中没有,从defaults找到)

# 修改只影响第一个字典
combined['new_key'] = 'val' # 写入overrides
del combined['new_key'] # 只能删除第一个字典中的键

# 新增一层(常用于作用域模拟)
child = combined.new_child({'color': 'green'})

# 示例:命令行参数 > 环境变量 > 默认配置 的优先级
import os
cli_args = {'debug': True}
env_vars = {'host': 'localhost'}
defaults = {'host': '0.0.0.0', 'port': 8080, 'debug': False}
config = ChainMap(cli_args, env_vars, defaults)
print(config['debug']) # True(CLI优先)
print(config['host']) # 'localhost'(env优先)
print(config['port']) # 8080(从defaults取)

bisect 二分查找模块

只能用于已排序的数组,所有操作时间复杂度 O(log n)。

底层实现

纯二分搜索,无额外数据结构:

  • bisect_left/right:O(log n) 查找,O(1) 额外空间
  • insort_left/right:O(log n) 查找 + O(n) 插入(list 中间插入需移动元素)
# insort 的完整复杂度
bisect(查找位置): O(log n)
list.insert(插入): O(n) ← 瓶颈!
总计:O(n)

若需要频繁有序插入,考虑用 SortedList(sortedcontainers 库)→ O(log n)
操作时间复杂度空间复杂度
bisect_left/rightO(log n)O(1)
insort_left/rightO(n)(插入移位)O(1)
import bisect

a = [1, 2, 4, 4, 5, 8]

# 查找插入位置(不实际插入)
bisect.bisect_left(a, 4) # => 2,若存在相等元素,插入到左侧(第一个4的位置)
bisect.bisect_right(a, 4) # => 4,若存在相等元素,插入到右侧(最后一个4的后面)
bisect.bisect(a, 4) # => 4,等价于 bisect_right

# 实际插入(保持有序)
bisect.insort_left(a, 3) # 将3插入,保持升序,相等元素插入到左侧
bisect.insort_right(a, 3) # 将3插入,保持升序,相等元素插入到右侧
bisect.insort(a, 3) # 等价于 insort_right

# 参数 lo, hi:限制搜索范围
bisect.bisect_left(a, x, lo=0, hi=len(a)) # 只在 a[lo:hi] 范围内搜索

核心用法:左右边界查找

a = [1, 2, 4, 4, 4, 5, 8]

# 查找第一个 >= x 的位置(左边界)
def lower_bound(a, x):
return bisect.bisect_left(a, x) # 等价于 C++ lower_bound

# 查找第一个 > x 的位置(右边界)
def upper_bound(a, x):
return bisect.bisect_right(a, x) # 等价于 C++ upper_bound

# 查找最后一个 <= x 的位置
def find_last_le(a, x):
idx = bisect.bisect_right(a, x) - 1
return idx if idx >= 0 else -1

# 判断x是否在数组中
def contains(a, x):
idx = bisect.bisect_left(a, x)
return idx < len(a) and a[idx] == x

# 示例:统计有序数组中x出现的次数
def count_occurrences(a, x):
return bisect.bisect_right(a, x) - bisect.bisect_left(a, x)

a = [1, 2, 4, 4, 4, 5, 8]
print(count_occurrences(a, 4)) # 3

算法应用(LeetCode 2070 最大美丽值)

# 排序后用bisect代替手写二分,代码更简洁
import bisect

def maximumBeauty(items, queries):
items.sort()
prices = [p for p, _ in items]
max_beauty = []
cur_max = 0
for _, b in items:
cur_max = max(cur_max, b)
max_beauty.append(cur_max)

result = []
for q in queries:
idx = bisect.bisect_right(prices, q) - 1
result.append(max_beauty[idx] if idx >= 0 else 0)
return result

heapq 堆模块

堆是完全二叉树,用数组存储。Python的heapq实现的是小顶堆(最小值在堆顶)。

底层实现

堆用数组(list)存储完全二叉树,节点 i 的父子关系:

父节点:(i - 1) // 2
左子节点:2 * i + 1
右子节点:2 * i + 2

核心操作

  • 上浮(sift up):插入元素到末尾,反复与父节点比较,若比父小则交换 → heappush O(log n)
  • 下沉(sift down):弹出堆顶,将末尾元素移到堆顶,反复与较小子节点交换 → heappop O(log n)

heapify 为什么是 O(n) 而非 O(n log n)?

从最后一个非叶节点(n//2 - 1)开始向前逐个做 sift down:
- 底层节点高度为 0,工作量少;高层节点少,工作量多
- 数学证明:∑ (n/2^k) * k = O(n),不是 O(n log n)
操作时间复杂度说明
heappush(h, x)O(log n)sift up
heappop(h)O(log n)sift down
h[0] 查看堆顶O(1)不修改堆
heapify(lst)O(n)非 O(n log n)!
heappushpop(h, x)O(log n)比分开调用快(少一次 sift)
heapreplace(h, x)O(log n)同上
nsmallest(k, lst)O(n log k)维护大小为 k 的堆
nlargest(k, lst)O(n log k)维护大小为 k 的堆

nsmallest vs sorted:k 很小时用 nsmallest(O(n log k));k 接近 n 时用 sorted(O(n log n),但常数更小)。CPython 内部也是这样判断的。

类型核心规则堆顶
小顶堆父节点值 ≤ 所有子节点值最小值
大顶堆父节点值 ≥ 所有子节点值最大值

基础操作

import heapq

# 创建堆(两种方式)
heap = []
heapq.heappush(heap, x) # 插入元素并维持堆结构,O(log n)
min_val = heapq.heappop(heap) # 弹出最小值,O(log n)
min_val = heap[0] # 查看堆顶(不弹出),O(1)

# 原地堆化(O(n),比逐个push高效)
arr = [5, 4, 3, 2, 1]
heapq.heapify(arr) # 将列表原地转为小顶堆

# 进阶操作
heapq.heappushpop(heap, x) # 先插入x,再弹出最小值(原子操作,高效)
heapq.heapreplace(heap, x) # 先弹出最小值,再插入x(要求堆非空)
# 区别:heappushpop 保证弹出的是"插入x后的最小值",heapreplace 保证堆顶先被弹出

# Top K问题
heapq.nsmallest(k, iterable) # 返回最小的k个元素(已排序列表)
heapq.nlargest(k, iterable) # 返回最大的k个元素(已排序列表)
heapq.nsmallest(k, iterable, key=lambda x: x[1]) # 支持自定义key

# 合并多个有序堆/迭代器(返回迭代器,惰性求值)
heapq.merge(*iterables, key=None, reverse=False) # 合并多个已排序序列
list(heapq.merge([1,3,5], [2,4,6])) # [1, 2, 3, 4, 5, 6]

大顶堆(插入负值)

# Python没有原生大顶堆,通过插入负值模拟
max_heap = []
heapq.heappush(max_heap, -x) # 插入时取负
max_val = -heapq.heappop(max_heap) # 弹出后取负

# 示例:维护前k大的数
k = 3
heap = []
for x in [3, 1, 4, 1, 5, 9, 2, 6]:
heapq.heappush(heap, -x)

top_k = [-heapq.heappop(heap) for _ in range(k)]
print(top_k) # [9, 6, 5]

元组堆(多键排序)

# heapq支持元组,按元组元素依次比较
import heapq

heap = []
heapq.heappush(heap, (priority, task_id, task)) # 先按priority排序

# 示例:Dijkstra最短路
h = [(0, start)] # (距离, 节点)
while h:
dist, node = heapq.heappop(h)
for neighbor, weight in graph[node]:
if dist + weight < dis[neighbor]:
dis[neighbor] = dist + weight
heapq.heappush(h, (dis[neighbor], neighbor))

堆排序

def heap_sort(arr):
heapq.heapify(arr)
return [heapq.heappop(arr) for _ in range(len(arr))]

print(heap_sort([7, 2, 5, 1, 8, 3])) # [1, 2, 3, 5, 7, 8]

itertools 模块

accumulate 前缀和/前缀积

from itertools import accumulate
import operator

nums = [1, 2, 3, 4, 5]

# 默认:前缀和
list(accumulate(nums)) # [1, 3, 6, 10, 15]

# 指定运算:前缀积
list(accumulate(nums, operator.mul)) # [1, 2, 6, 24, 120]

# 指定初始值(Python 3.8+)
list(accumulate(nums, initial=0)) # [0, 1, 3, 6, 10, 15](长度多1)

# 前缀最大值
list(accumulate(nums, max)) # [1, 2, 3, 4, 5]
list(accumulate([3,1,4,1,5,9], max)) # [3, 3, 4, 4, 5, 9]

# 应用:差分数组还原(差分求前缀和得原数组)
diff = [2, 1, -1, 3, -2]
original = list(accumulate(diff)) # [2, 3, 2, 5, 3]

# 应用:统计差分数组中被覆盖的点
diff = [0] * (max_end + 2)
for start, end in intervals:
diff[start] += 1
diff[end + 1] -= 1
covered_count = sum(s > 0 for s in accumulate(diff))

chain 链式迭代

from itertools import chain

# 连接多个可迭代对象(不创建新列表,惰性求值)
list1 = [1, 2, 3]
tuple1 = ('a', 'b')
str1 = "XY"
result = list(chain(list1, tuple1, str1))
# [1, 2, 3, 'a', 'b', 'X', 'Y']

# 展平嵌套列表(chain.from_iterable)
nested = [[1, 2], [3, 4, 5], [6]]
flat = list(chain.from_iterable(nested))
# [1, 2, 3, 4, 5, 6]

# 算法应用:提取多个栈的所有元素
stacks = [[1, 2], [3], [4, 5, 6]]
all_indices = sorted(chain.from_iterable(stacks))

其他常用 itertools

from itertools import product, permutations, combinations, combinations_with_replacement

# 笛卡尔积(多层嵌套循环的替代)
list(product([1,2], [3,4])) # [(1,3),(1,4),(2,3),(2,4)]
list(product('AB', repeat=2)) # [('A','A'),('A','B'),('B','A'),('B','B')]

# 全排列(n个元素取r个的全排列)
list(permutations([1,2,3], 2)) # [(1,2),(1,3),(2,1),(2,3),(3,1),(3,2)]

# 组合(不重复,不考虑顺序)
list(combinations([1,2,3,4], 2)) # [(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]

# 可重复组合
list(combinations_with_replacement([1,2,3], 2)) # [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]

functools 模块 —— lru_cache 记忆化

lru_cache(Least Recently Used Cache)是 Python 提供的记忆化(Memoization)装饰器,用于缓存函数的返回值。

核心作用

作用说明
避免重复计算相同参数的调用直接从缓存获取结果,不再执行函数体
优化递归递归问题(如斐波那契、动态规划)中大量子问题被重复计算,记忆化将指数级复杂度降为多项式级
提升性能频繁调用的纯函数可大幅减少执行时间
控制内存通过 maxsize 限制缓存大小,防止内存无限增长

基本用法

from functools import lru_cache

@lru_cache(maxsize=128) # 最多缓存128个不同参数的结果
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

# 性能对比
# 无缓存:O(2^n),fib(35) 需要数秒
# 有缓存:O(n),fib(1000) 瞬间完成

# 查看缓存统计
print(fibonacci.cache_info()) # CacheInfo(hits=998, misses=1000, maxsize=128, currsize=128)
fibonacci.cache_clear() # 清空缓存

参数说明

参数说明
maxsize最大缓存条目数,设为 None 表示无限制。默认128。
typed设为 True 时,33.0 被视为不同参数。默认 False

算法应用

典型应用场景:递归 DFS、动态规划、字符串拆分等问题。

# 示例:记忆化 DFS
@lru_cache(maxsize=None)
def dfs(state):
if 终止条件:
return 结果
# ... 递归调用 dfs(子状态)

相关题目:LeetCode 139. 单词拆分

注意事项

# ❌ 错误:参数必须可哈希
@lru_cache
def foo(lst: list): ... # list 不可哈希,会报错

# ✅ 正确:使用可哈希类型
@lru_cache
def foo(tup: tuple): ... # tuple 可哈希
  • 仅适用于纯函数:相同输入永远产生相同输出,无副作用
  • 参数必须可哈希listdict 等不可哈希类型需转为 tuplefrozenset
  • 内存权衡maxsize=None 可能占用大量内存,谨慎使用

常用数学函数

math 模块

import math

# 无穷大(推荐写法)
inf = math.inf # 正无穷
neg_inf = -math.inf # 负无穷
inf = float('inf') # 不需要导入math
neg_inf = float('-inf')

# 常用函数
math.floor(x) # 向下取整
math.ceil(x) # 向上取整
math.sqrt(x) # 平方根(返回float)
math.isqrt(x) # 整数平方根(向下取整,返回int)
math.gcd(a, b) # 最大公约数(Python 3.5+)
math.lcm(a, b) # 最小公倍数(Python 3.9+)
math.log(x) # 自然对数
math.log2(x) # 以2为底的对数
math.log10(x) # 以10为底的对数
math.pow(x, y) # x的y次方(返回float)
math.factorial(n) # n的阶乘
math.comb(n, k) # 组合数 C(n,k)(Python 3.8+)
math.perm(n, k) # 排列数 P(n,k)(Python 3.8+)

# 常用内置函数(无需导入)
abs(x) # 绝对值
pow(x, y) # x的y次方(整数运算不丢精度)
pow(x, y, mod) # 快速幂取模,等价于 x**y % mod
divmod(a, b) # 返回 (a//b, a%b) 元组
round(x, n) # 四舍五入,n为小数位数

无穷大的算法应用

# 初始化最大值/最小值的技巧
min_val = float('inf') # 比较时任何实数都比它小
max_val = float('-inf') # 比较时任何实数都比它大

# 验证BST(用±inf传递边界)
def isValidBST(root, left=-math.inf, right=math.inf):
if root is None:
return True
x = root.val
return left < x < right and \
isValidBST(root.left, left, x) and \
isValidBST(root.right, x, right)

# Dijkstra初始化
dis = [float('inf')] * n
dis[start] = 0

列表 vs 数组 vs 链表

特性Python列表array.array链表
存储方式动态数组(连续内存)连续内存非连续内存
随机访问O(1)O(1)O(n)
头部插入O(n)O(n)O(1)
尾部插入O(1)均摊O(1)均摊O(1)(已知尾节点)
内存开销较小最小较大(需存储指针)
元素类型任意固定任意
import array

# array模块用于大规模数值计算,元素类型固定,内存比list省
arr = array.array('i', [1, 2, 3]) # 'i'表示有符号整数
arr = array.array('d', [1.0, 2.5]) # 'd'表示双精度浮点数

# 常用类型码
# 'b': 有符号字节 'B': 无符号字节
# 'i': 有符号整数 'I': 无符号整数
# 'f': 单精度浮点 'd': 双精度浮点

内置函数速查

# 排序相关
sorted(iterable, key=None, reverse=False) # 返回新列表
lst.sort(key=None, reverse=False) # 原地排序

# 排序key技巧
lst.sort(key=lambda x: x[1]) # 按第二个元素排序
lst.sort(key=lambda x: (x[1], x[0])) # 多键排序
lst.sort(key=lambda x: -x) # 降序(不用reverse=True)
import functools
lst.sort(key=functools.cmp_to_key(compare)) # 自定义比较函数

# 常用内置函数
enumerate(iterable, start=0) # 返回(索引, 元素)对
zip(*iterables) # 并行迭代多个序列
map(func, iterable) # 对每个元素应用函数
filter(func, iterable) # 过滤满足条件的元素
any(iterable) # 任一为真则返回True
all(iterable) # 全部为真则返回True
sum(iterable, start=0) # 求和(start为初始值)
min(iterable, key=None) # 最小值
max(iterable, key=None) # 最大值

# 示例
nums = [3, 1, 4, 1, 5]
print(list(enumerate(nums, 1))) # [(1,3),(2,1),(3,4),(4,1),(5,5)]
print(list(zip([1,2,3], 'abc'))) # [(1,'a'),(2,'b'),(3,'c')]
print(list(map(str, nums))) # ['3','1','4','1','5']
print(list(filter(lambda x: x>2, nums))) # [3, 4, 5]
print(any(x > 4 for x in nums)) # True
print(all(x > 0 for x in nums)) # True
加载评论中...