前缀和与差分:一维、二维模板
总结一维和二维前缀和、差分数组的定义、区间查询与区间修改模板。
前缀和适合解决“多次查询固定数组的区间和”,差分适合解决“多次对数组区间进行相同修改”。它们的共同点是把批量操作转换为少量边界操作,再通过一次遍历恢复结果。
本文统一使用半开区间 [left, right),并使用长度为 n + 1 的辅助数组处理边界,这样可以避免大量 left - 1 的特殊判断。
一、一维前缀和
1.1 定义
给定数组 nums,定义:
prefix[0] = 0
prefix[i + 1] = nums[0] + nums[1] + ... + nums[i]
于是区间 [left, right) 的和为:
prefix[right] - prefix[left]
1.2 Python 模板
def build_prefix(nums: list[int]) -> list[int]:
prefix = [0] * (len(nums) + 1)
for i, value in enumerate(nums):
prefix[i + 1] = prefix[i] + value
return prefix
def range_sum(prefix: list[int], left: int, right: int) -> int:
"""返回 nums[left:right] 的元素和。"""
return prefix[right] - prefix[left]
构造前缀和的时间复杂度为 O(n),每次区间查询的时间复杂度为 O(1)。
相关题目:1480. 一维数组的动态和、303. 区域和检索 - 数组不可变。
二、二维前缀和
2.1 定义
对于矩阵 matrix,定义:
prefix[i + 1][j + 1]
表示左上角为 (0, 0)、右下角为 (i, j) 的矩形区域和。
构造公式为:
prefix[i + 1][j + 1] =
matrix[i][j]
+ prefix[i][j + 1]
+ prefix[i + 1][j]
- prefix[i][j]
其中最后一项用于去除左上角区域被重复计算的部分。
2.2 Python 模板
def build_matrix_prefix(matrix: list[list[int]]) -> list[list[int]]:
if not matrix or not matrix[0]:
return [[0]]
rows = len(matrix)
cols = len(matrix[0])
prefix = [[0] * (cols + 1) for _ in range(rows + 1)]
for i in range(rows):
for j in range(cols):
prefix[i + 1][j + 1] = (
matrix[i][j]
+ prefix[i][j + 1]
+ prefix[i + 1][j]
- prefix[i][j]
)
return prefix
def matrix_sum(
prefix: list[list[int]],
top: int,
left: int,
bottom: int,
right: int,
) -> int:
"""返回 matrix[top:bottom][left:right] 的元素和。"""
return (
prefix[bottom][right]
- prefix[top][right]
- prefix[bottom][left]
+ prefix[top][left]
)
这里的 bottom 和 right 都是开区间边界。例如查询包含第 0 到第 2 行、第 1 到第 3 列的区域,应传入 (0, 1, 3, 4)。
相关题目:304. 二维区域和检索 - 矩阵不可变。
三、一维差分数组
3.1 定义
差分数组 diff 定义为:
diff[0] = nums[0]
diff[i] = nums[i] - nums[i - 1] (i > 0)
对差分数组求前缀和,就可以恢复原数组。
3.2 区间增加
如果要让原数组的 [left, right) 区间全部增加 value,只需要修改两个边界:
diff[left] += value
最后对 diff 求一次前缀和,即可得到所有修改后的元素。
3.3 Python 模板
def apply_range_add(
length: int, operations: list[tuple[int, int, int]]
) -> list[int]:
"""operations 中的区间均为 [left, right)。"""
diff = [0] * (length + 1)
for left, right, value in operations:
diff[left] += value
diff[right] -= value
result = [0] * length
current = 0
for i in range(length):
current += diff[i]
result[i] = current
return result
每次区间修改的时间复杂度为 O(1),全部修改完成后恢复数组的时间复杂度为 O(n)。
相关题目:1109. 航班预订统计。题目使用闭区间和从 1 开始的编号,套用模板时需要先转换为半开区间和从 0 开始的下标。
四、二维差分数组
4.1 区域增加的四个边界
对于矩阵的半开区域 [top, bottom) × [left, right) 增加 value,修改二维差分数组的四个位置:
diff[top][left] += value
这四次修改通过二维前缀和抵消矩形外的影响,只保留目标区域。
4.2 Python 模板
def apply_matrix_range_add(
rows: int,
cols: int,
operations: list[tuple[int, int, int, int, int]],
) -> list[list[int]]:
"""operation 格式为 (top, left, bottom, right, value)。"""
diff = [[0] * (cols + 1) for _ in range(rows + 1)]
for top, left, bottom, right, value in operations:
diff[top][left] += value
diff[bottom][left] -= value
diff[top][right] -= value
diff[bottom][right] += value
result = [[0] * cols for _ in range(rows)]
for i in range(rows):
for j in range(cols):
top = result[i - 1][j] if i > 0 else 0
left = result[i][j - 1] if j > 0 else 0
diagonal = result[i - 1][j - 1] if i > 0 and j > 0 else 0
result[i][j] = top + left - diagonal + diff[i][j]
return result
相关题目:2536. 子矩阵元素加 1。
五、如何选择
| 问题类型 | 适合的方法 | 单次操作复杂度 |
|---|---|---|
| 固定数组,多次查询区间和 | 前缀和 | O(1) 查询 |
| 多次对数组区间增加相同值 | 一维差分 | O(1) 修改 |
| 固定矩阵,多次查询子矩阵和 | 二维前缀和 | O(1) 查询 |
| 多次对矩阵区域增加相同值 | 二维差分 | O(1) 修改 |
如果查询和修改操作需要交替进行,静态前缀和或差分数组通常不再适用,此时可以考虑树状数组或线段树。
六、总结
- 前缀和把区间求和转换为两个前缀值的减法。
- 差分数组把区间修改转换为边界上的常数次修改。
- 使用长度为
n + 1的辅助数组,可以统一处理数组边界。 - 一维和二维的思想相同,二维公式需要额外减去重复计算的左上区域。