← 返回题目列表

什么是前缀和?如何用它 O(1) 查询区间和?

高频 简单 第 1 / 20 题 更新于 2026/07/28
前缀和区间和预处理

简化版

前缀和是预处理技巧:先算出「从头到每个位置的累加和」存进 prefix 数组,之后任意区间 [l, r] 的和就能 O(1) 算出来——用 prefix[r+1] - prefix[l]。预处理 O(n),之后每次查询 O(1)。适合「数组不变、多次查询区间和」的场景,把「每次查询 O(n) 累加」优化成 O(1)。

详细版

定义:prefix[i] = 数组前 i 个元素的和(a[0] + a[1] + ... + a[i-1]),约定 prefix[0] = 0

int[] prefix = new int[n + 1];        // 长度 n+1,prefix[0]=0
for (int i = 0; i < n; i++)
    prefix[i + 1] = prefix[i] + a[i]; // 递推:前 i+1 个和 = 前 i 个和 + a[i]

// 区间 [l, r](含两端)的和:
int rangeSum = prefix[r + 1] - prefix[l];
  • prefix[i+1] = prefix[i] + a[i]:一趟 O(n) 预处理。
  • 区间和 [l, r] = prefix[r+1] - prefix[l]:两个前缀和相减,O(1)。
  • 用长度 n+1prefix[0]=0 的设计,能统一处理「从下标 0 开始」的区间,避免边界特判。

完整版教学

一、前缀和解决什么问题

设想一个数组固定不变,但要反复查询「某个区间的元素和」。朴素做法每次查询都从 l 累加到 r,单次 O(n),q 次查询就是 O(nq),很慢。前缀和的思路是:先花 O(n) 预处理出所有「前缀的累加和」,之后每次区间查询只需一次减法 O(1)。用「一次预处理」换来「每次查询常数时间」,是典型的空间/预处理换时间

二、核心公式:区间和 = 两个前缀和相减

前缀和的精髓是一个减法。prefix[i] 表示「前 i 个元素的和」,那么区间 [l, r](含两端)的和就是:

sum(l..r) = (前 r+1 个的和) - (前 l 个的和) = prefix[r+1] - prefix[l]

直观理解:prefix[r+1] 包含了 a[0..r],prefix[l] 包含了 a[0..l-1],两者相减,a[0..l-1] 被抵消,剩下的正好是 a[l..r]。就像「到 r 的路程」减去「到 l-1 的路程」等于「l 到 r 的路程」。

三、为什么用 prefix[0]=0 和长度 n+1

前缀和数组的下标设计是最容易出错的地方。推荐用长度 n+1prefix[0]=0 的定义:

  • prefix[0] = 0(前 0 个元素的和)。
  • prefix[i] = a[0] + ... + a[i-1](前 i 个)。

好处是区间公式统一为 prefix[r+1] - prefix[l],连「从下标 0 开始的区间」也不用特判(sum(0..r) = prefix[r+1] - prefix[0] = prefix[r+1])。如果用 prefix[i] = a[0]+...+a[i](长度 n)的定义,查 sum(0..r) 时要特判 l==0,容易漏。所以记住这个「偏移一位、补 0」的定式,能避开大量边界 bug。

四、走一个例子

a =      [3, 1, 4, 1, 5]
prefix = [0, 3, 4, 8, 9, 14]   (prefix[i] = 前 i 个的和)
查询区间 [1, 3](即 a[1]+a[2]+a[3] = 1+4+1 = 6):
  prefix[3+1] - prefix[1] = prefix[4] - prefix[1] = 9 - 3 = 6 ✓

五、适用场景与局限

  • 适合:数组静态不变多次查询区间和。预处理一次,查询 O(1)。
  • 不适合:数组频繁单点修改——每次改一个元素,后面所有前缀和都要更新 O(n)。这种「既要单点改、又要区间查」的场景,用树状数组(BIT)或线段树(单点改 + 区间查都 O(log n))。
  • 区间批量修改用它的逆运算——差分数组(见差分专题)。

前缀和、差分、树状数组是「区间操作」的一套工具,按「改什么、查什么」来选。

六、前缀和是一大类题的基础

前缀和不只是「查区间和」,更是一个思维模块,衍生出很多高频题:

  • 和为 K 的子数组:前缀和 + 哈希表,把「找和为 k 的区间」转成「找两个差为 k 的前缀和」。
  • 二维前缀和:O(1) 查子矩阵和。
  • 前缀异或和:区间异或。
  • 前缀积:除自身外的乘积。

核心都是「把区间操作转化为两个前缀量的运算」。

七、从公式证明到手算闭环

这道题成立的核心是:prefix[i] 等于原数组前 i 个元素之和,区间 [l,r] 的公共前缀恰好被相减消掉。先明确每个数组槽或哈希键的数学含义,代码中的下标偏移才不是死记硬背。

prefix[0] = 0
prefix[i + 1] = prefix[i] + a[i]
sum(l, r) = prefix[r + 1] - prefix[l]

带数字推演:数组 [2,-1,3,5] 的前缀为 [0,2,1,4,9],所以 [1,3] 的和是 9-2=7。手算时同时列出原数组、辅助状态和本轮新增答案,能够直接发现端点偏一、初始化遗漏以及更新顺序错误。

核对维度本题结论
正确性依据prefix[i] 等于原数组前 i 个元素之和,区间 [l,r] 的公共前缀恰好被相减消掉
复杂度一次扫描 O(n) 预处理,单次查询 O(1),额外空间 O(n)
关键边界r+1 是最常见的偏移错误;累计值可能超过 int;静态查询适用,频繁单点更新应换树状数组或线段树

记忆钩子:不要先背代码,先说清辅助状态“代表哪一段”;公式只是把重叠部分消掉或把边界影响传播出去。

八、实现边界与测试策略

实现时最需要警惕的是:r+1 是最常见的偏移错误;累计值可能超过 int;静态查询适用,频繁单点更新应换树状数组或线段树。这不是语法细节,而是决定算法是否仍满足题目语义的前提。

提交前应分别验证:

  • 空数组或最小合法规模,确认哨兵位置和初始化。
  • 查询或更新紧贴左、上边界,确认没有访问负下标。
  • 查询或更新紧贴右、下边界,确认“终点后一位”不会越界。
  • 包含 0、负数或重复前缀的样例,确认频次与取模语义。
  • 大数输入,确认累计和、乘积或答案数量的整数类型足够。

如果需求从离线变成在线,或从单次恢复变成更新查询交错,原方法可能不再合适。此时应根据操作类型改用树状数组、线段树、二维结构或其他能维护动态状态的数据结构,而不是强行沿用静态前缀模型。

九、常见误区与追问

  • 误区:前缀数组必须与原数组等长。 长度 n+1 并让 prefix[0]=0,能统一 l=0 的查询。
  • 误区:前缀和只能处理正数。 加减法对负数同样成立,受影响的是滑动窗口的单调性。
  • 误区:一次查询也应该先建前缀和。 只有单次查询时直接扫描可能更省空间。
  • 追问:为什么区间和要减 prefix[l]? 它恰好包含下标 0 到 l-1 的公共部分。
  • 追问:原数组更新后怎么办? 普通前缀和后缀都失效;高频更新用树状数组或线段树。
  • 追问:怎样防止溢出? 按最大元素与 n 估算总和上界,必要时使用 64 位整数。

十、加强记忆

前缀和是预处理:prefix[i] = 前 i 个元素和(prefix[0]=0,长度 n+1),预处理 O(n)。区间 [l,r] 的和 = prefix[r+1] - prefix[l](两前缀相减,抵消掉 a[0..l-1]),查询 O(1)。用「偏移一位补 0」的下标设计避免边界特判。适合静态数组多次区间查询;频繁单点改用树状数组/线段树,区间批量改用差分。它是「和为K子数组、二维前缀和、前缀异或」等一大类题的基础。