LeetCode 303. 区域和检索 - 数组不可变
题目描述
题意分析
要设计一个类:构造时接收一个整数数组,之后反复调用
sumRange(left, right)返回闭区间[left, right]内所有元素之和。
这是一道设计题,所以要看的不是「一次查询多快」,而是构造与查询的代价怎么分摊。题面里写明了
sumRange最多会被调用 $3 \times 10^4$ 次、数组长度也在 $10^4$ 量级,两者相乘是 $3 \times 10^8$——这正是在告诉你「每次查询现场累加」会超时,必须把公共计算搬到构造函数里做一次。
类名和题目标题里的「不可变」三个字是最强的信号:数组在构造之后不会被修改。这意味着预处理结果一旦算好就永久有效,不需要考虑更新时如何维护,也就不必上树状数组或线段树这类支持单点修改的结构。可修改的版本是另一道题(307),两者的分水岭就在这里。
区间是闭区间,两端都要计入。这一点看似琐碎,却决定了下标偏移里那个
+1写在哪里,是本题唯一真正需要小心的地方。
边界要盯住:
left与right可以相等,此时区间只含一个元素;left可以是 0,区间从数组开头起算;right可以是最后一个下标;元素可以是负数,所以不能用任何「和非负」的假设去剪枝。
解法:前缀和
核心思路
数组不会更新,却会被反复查询区间和。若每次从
left累加到right,单次查询为 $O(n)$;更合适的做法是在构造对象时预处理前缀和,把重复计算提前完成。定义
\[prefix[right+1] - prefix[left]\]prefix[i]为前i个元素之和,令prefix[0] = 0。闭区间[left, right]的和就是:两个前缀都包含
left之前的部分,相减后只剩查询区间。不变量:构造完成后,对任意
i,prefix[i]都严格表示nums[0...i-1]的和。多保留的prefix[0]让从下标 0 开始的查询也无需特判。
解题步骤
- 创建长度为
nums.length + 1的前缀和数组。- 对每个下标
i,计算prefix[i+1] = prefix[i] + nums[i]。- 查询
[left, right]时返回prefix[right+1] - prefix[left]。例如
nums = [-2,0,3,-5,2,-1],查询[2,5]时计算prefix[6] - prefix[2] = -3 - (-2) = -1。
代码实现
class NumArray {
private final int[] prefix;
public NumArray(int[] nums) {
prefix = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
prefix[i + 1] = prefix[i] + nums[i];
}
}
public int sumRange(int left, int right) {
return prefix[right + 1] - prefix[left];
}
}
type NumArray struct {
prefix []int
}
func Constructor(nums []int) NumArray {
prefix := make([]int, len(nums)+1)
for i, value := range nums {
prefix[i+1] = prefix[i] + value
}
return NumArray{prefix: prefix}
}
func (this *NumArray) SumRange(left int, right int) int {
return this.prefix[right+1] - this.prefix[left]
}
复杂度分析
- 构造时间复杂度:$O(n)$。
- 查询时间复杂度:$O(1)$。
- 空间复杂度:$O(n)$,用于保存前缀和数组。
关键点总结
- 不可变数组加多次区间查询,是前缀和的直接使用场景。
prefix[i]表示前i个元素,而不是以下标i结尾的前缀。- 长度取
n + 1可以用同一公式覆盖left = 0。- 闭区间右端点在前缀数组中对应
right + 1。
易错点总结
- 返回
prefix[right] - prefix[left]:会漏掉右端点,并产生整体错位。- 返回
prefix[right+1] - prefix[left+1]:会漏掉左端点。- 只开长度
n的数组:无法自然表示空前缀,也容易在right = n-1时越界。- 每次查询重新累加:结果正确,但没有利用数组不可变,单次查询仍是 $O(n)$。
- 把问题当作动态数组维护:题目没有更新操作,无须树状数组或线段树。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 304. 二维区域和检索 - 矩阵不可变 | 中等 | 二维版本,查询要用容斥原理做四项加减,边界同样靠多开一行一列消掉 |
| 307. 区域和检索 - 数组可修改 | 中等 | 允许单点更新,前缀和失效,必须换成树状数组或线段树 |
| 560. 和为 K 的子数组 | 中等 | 同样把区间和写成前缀差,但要配合哈希表反查,目标是统计方案数 |
| 724. 寻找数组的中心下标 | 简单 | 左右两侧和相等,用一个总和减去左前缀即可,无需完整前缀数组 |
| 238. 除了自身以外数组的乘积 | 中等 | 把前缀和换成前缀积并配合后缀积,且不许用除法,考察同一套预处理思想 |
| 1310. 子数组异或查询 | 中等 | 运算换成异或,因其自逆性同样支持「前缀相消」,是前缀技巧的推广 |
| 1109. 航班预订统计 | 中等 | 反向操作:多次区间加、最后一次性查询,用差分数组再求前缀和 |
| 370. 区间加法 | 中等 | 纯差分数组模板题,正好与本题构成「前缀和与差分互逆」的一对 |