前缀和算法是一种在数组处理中广泛应用的优化技术,其核心思想是通过预先计算并存储中间结果,将区间和的查询时间从 O (n) 降至 O (1)。这种 “空间换时间” 的策略在处理多次区间和查询的场景中能显著提升效率,是编程面试和算法竞赛中的高频考点。
一、一维前缀和
1.1 问题引入
对于一个给定的数组nums,如果需要频繁查询 “区间[l, r]内所有元素的和”(其中l和r为数组下标,且0 ≤ l ≤ r < n),最直接的方法是每次查询时遍历区间累加,时间复杂度为 O (r-l+1)。当查询次数q较大时(如q=10^5),总时间复杂度会达到 O (qn),可能导致超时。
前缀和算法通过一次 O (n) 的预处理,将所有区间和查询优化为 O (1),总时间复杂度降至 O (n+q),大幅提升效率。
1.2 原理与定义
一维前缀和数组prefix的定义为:
- prefix[0] = 0(哨兵元素,方便边界处理)
- prefix[i]表示数组nums中前i个元素的和(即nums[0]到nums[i-1]的和)
数学表达式为:
prefix[i] = nums[0] + nums[1] + ... + nums[i-1]
基于此定义,区间[l, r](对应数组元素nums[l]到nums[r])的和可通过以下公式计算:
sum(l, r) = prefix[r+1] - prefix[l]
1.3 示例说明
以数组nums = [1, 2, 3, 4, 5]为例:
- 前缀和数组prefix的计算过程:
-
- prefix[0] = 0
-
- prefix[1] = nums[0] = 1
-
- prefix[2] = nums[0] + nums[1] = 1+2=3
-
- prefix[3] = nums[0]+nums[1]+nums[2] = 1+2+3=6
-
- prefix[4] = 1+2+3+4=10
-
- prefix[5] = 1+2+3+4+5=15
- 若查询区间[1, 3](即元素2,3,4)的和:
-
- 根据公式:sum(1,3) = prefix[4] - prefix[1] = 10 - 1 = 9,与直接计算2+3+4=9结果一致。
1.4 实现步骤
(1)构建前缀和数组
- 初始化前缀和数组prefix,长度为n+1(n为原数组长度);
- 令prefix[0] = 0;
- 遍历原数组,通过递推公式prefix[i] = prefix[i-1] + nums[i-1]计算prefix[1..n]。
(2)区间和查询
对于区间[l, r],直接返回prefix[r+1] - prefix[l]。
1.5 Python 代码实现
def build_prefix(nums):
"""构建一维前缀和数组"""
n = len(nums)
prefix = [0] * (n + 1)
for i in range(1, n + 1):
prefix[i] = prefix[i - 1] + nums[i - 1] # 累加原数组元素
return prefix
def query_sum(prefix, l, r):
"""查询区间[l, r]的和(0 ≤ l ≤ r < len(nums))"""
if l < 0 or r >= len(prefix) - 1 or l > r:
return 0 # 处理非法输入
return prefix[r + 1] - prefix[l]
# 示例
nums = [1, 2, 3, 4, 5]
prefix = build_prefix(nums)
print(query_sum(prefix, 1, 3)) # 输出:9(2+3+4)
print(query_sum(prefix, 0, 4)) # 输出:15(1+2+3+4+5)
二、二维前缀和
2.1 问题扩展
在二维数组(矩阵)中,若需要查询 “子矩阵(x1, y1)到(x2, y2)内所有元素的和”(其中(x1,y1)为左上角坐标,(x2,y2)为右下角坐标),普通方法的时间复杂度为 O ((x2-x1+1)*(y2-y1+1)),效率更低。此时二维前缀和可将查询优化为 O (1)。
2.2 原理与定义
二维前缀和数组prefix的定义为:
- prefix[i][j]表示以(0,0)为左上角、(i-1,j-1)为右下角的子矩阵的和(即原矩阵中[0..i-1]行、[0..j-1]列的元素和)。
其构建公式基于容斥原理:
prefix[i][j] = matrix[i-1][j-1]
+ prefix[i-1][j] # 上方子矩阵和
+ prefix[i][j-1] # 左方子矩阵和
- prefix[i-1][j-1] # 减去重复计算的左上角子矩阵和
对于子矩阵(x1,y1)到(x2,y2)(坐标从 0 开始),其和的计算公式为:
sum = prefix[x2+1][y2+1]
- prefix[x1][y2+1] # 减去上方多余区域
- prefix[x2+1][y1] # 减去左方多余区域
+ prefix[x1][y1] # 加回重复减去的区域
2.3 示例说明
对于矩阵:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
其二维前缀和数组prefix(4x4)的计算过程如下:
- prefix[0][] = 0,prefix[][0] = 0(边界初始化)
- prefix[1][1] = 1(仅包含 (0,0))
- prefix[1][2] = 1+2 = 3(包含 (0,0)、(0,1))
- prefix[2][2] = 1+2+4+5 = 12(包含 (0,0)-(1,1) 的子矩阵)
查询子矩阵(0,1)到(2,2)(即第二列到第三列、第一行到第三行)的和:
sum = prefix[3][3] - prefix[0][3] - prefix[3][1] + prefix[0][1]
= 45 - 0 - 12 + 0 = 33
验证:2+3+5+6+8+9 = 33,结果正确。
2.4 Python 代码实现
def build_2d_prefix(matrix):
"""构建二维前缀和数组"""
m = len(matrix)
if m == 0:
return []
n = len(matrix[0])
# 初始化(m+1)x(n+1)的前缀和数组,首行首列均为0
prefix = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1]
return prefix
def query_2d_sum(prefix, x1, y1, x2, y2):
"""查询子矩阵(x1,y1)到(x2,y2)的和(0 ≤ x1 ≤ x2 < m,0 ≤ y1 ≤ y2 < n)"""
if not prefix:
return 0
m, n = len(prefix)-1, len(prefix[0])-1
if x1 < 0 or x2 >= m or y1 < 0 or y2 >= n or x1 > x2 or y1 > y2:
return 0 # 处理非法输入
return prefix[x2+1][y2+1] - prefix[x1][y2+1] - prefix[x2+1][y1] + prefix[x1][y1]
# 示例
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
prefix_2d = build_2d_prefix(matrix)
print(query_2d_sum(prefix_2d, 0, 1, 2, 2)) # 输出:33(2+3+5+6+8+9)
三、应用场景与扩展
前缀和算法的核心是预处理区间和的中间结果,除了直接的区间和查询,还可用于解决以下问题:
- 子数组和等于 k 的数量(通过前缀和 + 哈希表优化);
- 二维区域和检索(如 LeetCode 304 题);
- 奇偶性前缀和(用于统计区间内奇偶元素的数量)。
在实际应用中,需注意前缀和数组的边界处理(如索引从 0 开始还是 1 开始)和数据溢出问题(当数组元素过大时,需使用 64 位整数类型)。
总结
前缀和算法通过一次预处理将区间和查询优化为常数时间,是 “空间换时间” 策略的经典案例。一维前缀和适用于线性数组,二维前缀和适用于矩阵,两者均遵循 “预处理→查询” 的两步流程。掌握前缀和算法不仅能提升代码效率,更能培养 “预计算中间结果” 的算法思维,为解决复杂问题奠定基础。