【算法图解】前缀和:区间查询神器
前缀和把“重复求和”变成一次预处理,让区间和查询从 O(n) 变成 O(1)。本文讲清一维/二维前缀和、差分数组的关系、常见题型(子数组和为 K、二维区域和)与实现细节。
16 分钟阅读
小明
【算法图解】前缀和:区间查询神器
你有没有遇到过这种问题:
- 给你一个数组
- 问很多次“从 i 到 j 的和是多少?”
如果每次都循环相加,复杂度就会爆。
前缀和的思想是:
把重复计算提前做掉,让每次查询只做 O(1)。
它是很多面试题的底层骨架。
1. 一维前缀和:定义与公式
定义:
pre[i]表示前 i 个元素的和(也可以定义成到 i 为止,关键是统一)
常用定义:
pre[0] = 0pre[i+1] = pre[i] + a[i]
这样区间 l, r 的和就是:
$$sum(l,r) = prer+1 - prel$$
2. 可运行代码
function buildPrefixSum(a) {
const pre = new Array(a.length + 1).fill(0)
for (let i = 0; i < a.length; i++) pre[i + 1] = pre[i] + a[i]
return pre
}
function rangeSum(pre, l, r) {
return pre[r + 1] - pre[l]
}
const a = [2, -1, 3, 5]
const pre = buildPrefixSum(a)
console.log(rangeSum(pre, 1, 3)) // -1 + 3 + 5 = 7
3. 典型题:子数组和为 K(前缀和 + 哈希表)
题意:统计有多少个子数组的和等于 K。
核心等式:
- 若
pre[j] - pre[i] = K - 则
pre[i] = pre[j] - K
遍历前缀和 pre[j] 时,用 Map 统计过去出现过多少个 pre[i]。
function subarraySumEqualsK(a, k) {
const count = new Map()
count.set(0, 1)
let pre = 0
let ans = 0
for (const x of a) {
pre += x
ans += count.get(pre - k) ?? 0
count.set(pre, (count.get(pre) ?? 0) + 1)
}
return ans
}
console.log(subarraySumEqualsK([1, 2, 3, -2, 2], 3))
4. 二维前缀和:矩阵区域和
二维版本同样是“预处理换 O(1) 查询”。
定义 pre[i][j] 为 (0,0) 到 (i-1,j-1) 子矩阵和。
查询矩形 (r1,c1) 到 (r2,c2):
$$S = pre[r2+1]c2+1 - pre[r1]c2+1 - pre[r2+1]c1 + pre[r1]c1$$
(容斥)
5. 和差分数组的关系
- 前缀和:支持区间查询
- 差分:支持区间更新(把更新延迟到最后一次性前缀求回去)
很多题会把两者组合使用。
6. 常见坑
- 前缀数组下标定义不一致导致 off-by-one
- 没初始化
pre[0]=0/ Map 里没放0->1 - 大数溢出(JS number 还好,其他语言要用 long)
总结
- 前缀和把区间和查询从 $O(n)$ 变成 $O(1)$。
- “子数组和为 K”是前缀和 + 哈希表的经典组合。
- 二维前缀和用容斥做矩形查询。