← 返回题目列表

不可变数组多次区间求和,为什么要预处理前缀和?

简单 第 16 / 20 题 更新于 2026/07/31
前缀和区间查询预处理

简化版

如果数组不变但要多次查询区间和,应该先预处理前缀和。

定义 prefix[i] 表示前 i 个元素的和,那么区间 [left, right] 的和是:

prefix[right + 1] - prefix[left]

预处理 O(n),每次查询 O(1)

详细版

不可变数组的关键是“更新没有,查询很多”。

如果每次查询都从 left 加到 right,单次是 O(n),查询次数多时会很慢。前缀和把重复计算提前做掉。

构造时:

prefix[0] = 0
prefix[i + 1] = prefix[i] + nums[i]

查询时:

sumRange(left, right) = prefix[right + 1] - prefix[left]

多加一个 prefix[0] = 0 可以避免 left = 0 的特判。

完整版教学

一、为什么多次查询要预处理

单次区间求和直接循环没有问题,但如果查询次数是 10^5,每次最多扫 10^5 个元素,最坏会到 10^10 级别操作。前缀和的想法是把每个位置之前的累计结果提前算好,之后任意区间都通过两个累计值相减得到。它用一次 O(n) 预处理,换来每次 O(1) 查询。

记忆钩子:数组不变、区间多问,先把“从头到这里的和”存下来。

二、prefix[i] 为什么表示前 i 个元素

推荐定义 prefix 长度为 n + 1,其中 prefix[0] = 0prefix[i] 表示 nums[0]nums[i-1] 的和。这样区间 [left, right] 的左边界刚好对应 prefix[left],右边界对应 prefix[right + 1]

数组nums[0]nums[1]nums[2]
前缀下标prefix[1]prefix[2]prefix[3]
含义前 1 个和前 2 个和前 3 个和

多出来的 prefix[0] 是为了让空前缀也有统一表示。

三、公式为什么成立

prefix[right + 1] 包含从 0right 的全部元素。prefix[left] 包含从 0left - 1 的元素。两者相减,前面公共部分被抵消,剩下的正好是 [left, right]

prefix[right + 1] = nums[0] + ... + nums[left-1] + nums[left] + ... + nums[right]
prefix[left]      = nums[0] + ... + nums[left-1]
差值              = nums[left] + ... + nums[right]

这就是前缀和所有区间查询公式的来源。

四、代码模板

可以封装成类:

class NumArray {
  constructor(nums) {
    this.prefix = new Array(nums.length + 1).fill(0)
    for (let i = 0; i < nums.length; i++) {
      this.prefix[i + 1] = this.prefix[i] + nums[i]
    }
  }

  sumRange(left, right) {
    return this.prefix[right + 1] - this.prefix[left]
  }
}

这个写法把查询的边界都统一掉,尤其是 left = 0 时不用额外分支。

五、复杂度收益怎么算

假设数组长度 n = 100000,查询次数 q = 100000

方法预处理单次查询总成本
每次循环求和O(1)O(n)O(nq)
前缀和O(n)O(1)O(n+q)

当查询很多时,前缀和收益非常明显。

六、常见误区与追问

  • 误区:prefix[i] 定义混乱。 一会儿表示到 i,一会儿表示前 i 个,会导致 +1/-1 全乱。
  • 误区:忘记 prefix[0] = 0。 没有空前缀时,查询从 0 开始的区间容易特判。
  • 误区:数组会更新也直接用普通前缀和。 更新会让后续前缀都失效,应考虑树状数组或线段树。
  • 追问:为什么空间是 O(n)? 需要保存每个位置的累计和,长度是 n + 1
  • 追问:如果元素有负数还能用吗? 可以,前缀和不依赖正负,只是累计加法。

这些问题考的是前缀和定义是否稳定。

七、加强记忆

不可变区间求和记成“前缀多一格,查询两数减”。prefix[0] = 0prefix[i] 表示前 i 个元素的和;查询 [l,r] 就是 prefix[r+1]-prefix[l]。只要数组不更新,多次查询就非常适合这种预处理模型。