【算法图解】前缀和:区间查询神器

前缀和把“重复求和”变成一次预处理,让区间和查询从 O(n) 变成 O(1)。本文讲清一维/二维前缀和、差分数组的关系、常见题型(子数组和为 K、二维区域和)与实现细节。

16 分钟阅读
小明

【算法图解】前缀和:区间查询神器

你有没有遇到过这种问题:

  • 给你一个数组
  • 问很多次“从 i 到 j 的和是多少?”

如果每次都循环相加,复杂度就会爆。

前缀和的思想是:

把重复计算提前做掉,让每次查询只做 O(1)。

它是很多面试题的底层骨架。


1. 一维前缀和:定义与公式

定义:

  • pre[i] 表示前 i 个元素的和(也可以定义成到 i 为止,关键是统一)

常用定义:

  • pre[0] = 0
  • pre[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”是前缀和 + 哈希表的经典组合。
  • 二维前缀和用容斥做矩形查询。