0. 应用场景
前缀和的应用场景是,需要对某个区间[i…j]频繁查询累计和,避免每次查询都遍历这个区间。
差分数组的应用场景是,需要对某个区间[i…j]频繁地加或减某一值,避免每次都遍历这个区间。
1. 概念
# 一维前缀和:
vector<int> nums;
vector<int> sums(nums.size() + 1, 0); // 空间比nums大1,前缀和整体右移一位, 这样计算不需要考虑数组越界
for (int i = 0; i < nums.size(); i++) {
sums[i + 1] = sums[i] + nums[i];
}
解决问题:长度为n的序列,m次询问,每次输入一对数a,b。输出从第a个数到第个数的和;
时间复杂度O(n+m)
计算区间和[l,r] :
int sum = sums[r + 1] - sums[l];
# 二维前缀和:
vector<vector<int>> nums;
vector<vector<int>> sums(nums.size() + 1, vector<int>(nums[0].size() + 1, 0));
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j < nums[0].size(); j++) {
sums[i + 1][j + 1] = sums[i][j + 1] + sums[i + 1][j] - sums[i][j] + nums[i][j];
}
}
解决问题:n行m列的整数矩阵,每次查询给出x1,y1,x2,y2表示一个子矩阵的左上角坐标和右下角坐标,每次输出子矩阵中所有数的和。
计算子矩阵和[x1,y1] --> [x2, y2]:
int sum = sums[x2 + 1][y2 + 1] - sums[x2 + 1][y1] - sums[x1][y2 + 1] + sums[x1][y1];
# 差分数组:
diff[i] = nums[i] - nums[i - 1];
由diff反推原数组, 差分数组的前缀和,即为原数组。
eg, 要对[i,...,j]区间内的元素整体+3,则令diff[i] += 3, diff[j + 1] -= 3即可,复杂度O(1)。
2. 前缀和
// 和为K的连续子数组的个数
// 前缀和
/*
* sums[i] = sums[i - 1] + nums[i];
* K = sums[i] - sums[j];
*/
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1; // 对于一开始的情况,下标0之前没有数据,可以认为前缀和为0,个数为1
int sums = 0;
int count = 0;
for (int i = 0; i < nums.size(); i++) {
sums += nums[i];
if (mp[sums - k]) { // if (mp.find(sums - k) != mp.end())
count += mp[sums - k];
}
mp[sums]++; // 遍历至此,前缀和为sums的次数又多了一次
}
return count;
}
};
3. 差分数组
// 航班预订统计:差分数组
class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
vector<int> answer(n);
vector<int> diff(n + 1); // +1 是为了防止最后一个数计算的时候,栈溢出
for (int i = 0; i < bookings.size(); i++) {
diff[bookings[i][0] - 1] += bookings[i][2];
diff[bookings[i][1] - 1 + 1] -= bookings[i][2];
}
answer[0] = diff[0];
for (int i = 1; i < n; i++) {
answer[i] = diff[i] + answer[i - 1];
}
return answer;
}
};