题目链接
303. 区域和检索 - 数组不可变
题目描述
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
1. 你可以假设数组不可变。
2. 会多次调用 sumRange 方法。
样例
给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
算法: 前缀和
给定长度 n 的序列
之后 S 数组可以支持 O(1) 求区间 [i, j] 的和,答案是 S[j+1] - S[i], 其中 0 <= i <= j <= n - 1。
建 S 数组的过程需要额外 O(n) 空间,和总共 O(n), 摊销 O(1) 的时间。
题目 304. 二维区域和检索 - 矩阵不可变 是前缀和二维的版本,建好 S 数组之后求两个点之间的矩形区域和时需要借助容斥原理,稍微有点麻烦。
代码(C++)
一维版本
class NumArray {
public:
NumArray(vector<int>& nums) {
if(nums.empty())
sums = vector<int>();
else
{
int n = nums.size();
sums = vector<int>(n + 1, 0);
for(int i = 1; i <= n; ++i)
sums[i] = sums[i - 1] + nums[i - 1];
}
}
int sumRange(int i, int j) {
// 0 <= i <= j <= n-1
if(sums.empty()) return 0;
return sums[j + 1] - sums[i];
}
private:
vector<int> sums;
};
二维版本
class NumMatrix {
public:
NumMatrix(vector<vector<int>>& matrix) {
if(matrix.empty())
sums = vector<vector<int> >();
else
{
int m = matrix.size();
int n = matrix[0].size();
sums = vector<vector<int> >(m + 1, vector<int>(n + 1, 0));
for(int i = 1; i <= m; ++i)
for(int j = 1; j <= n; ++j)
sums[i][j] = sums[i][j - 1] + sums[i - 1][j]
- sums[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
if(sums.empty()) return -1;
// row1 <= row2, col1 <= col2
return sums[row2 + 1][col2 + 1] - sums[row2 + 1][col1]
- sums[row1][col2 + 1] + sums[row1][col1];
}
private:
vector<vector<int> > sums;
};
引申1: 前缀的推广
1. 能推广的运算
满足区间减法的运算,区间 A = [i, j], 区间 B = [i, k] 区间 C = [k+1, j],那么有了大区间 A 上的结果 a 和其中一个小区间 B 上的结果 b, 要能够算出另一个小区间 C 上的结果 c。
异或:
模下乘法:
2. 前缀和 + 数据结构
问:
当扫描到 i 时,
上面的问题还可以有变种:
第1问:
把 unordered_set 改成 unordered_map 就可以
第2问:
把 unordered_set 改成线段树
第3问: 第一问的树形版本,437. 路径总和 III
dfs(前序遍历)时,栈里存的是当前节点到根的链,这条链上的和可以作为前缀和维护在 unordered_map 里。从左子树跳到右子树的时候,左子树的所有节点对应的前缀和要先从 unordered_map 中删掉。
引申2: 差分
前缀和序列
原序列
差分序列的好处是如果要对原序列的一个区间 [l, r] 上所有值加 val,原序列上要操作 r-l+1 次(a[l .. r] + val),在差分序列上只需要操作 2 次(b[l] + val, b[r+1] - val)。如果这种区间操作需要很多次,最后的查询只有一次的话,就非常适合在差分序列上操作。