前缀和数组主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和,而差分数组的适用场景是频繁对某个原始数组的某个区间进行增减。
举个例子,我给你输入一个数组 nums,然后又要求给区间 nums[2…6] 全部加 1,再给 nums[3…9] 全部减 3,再给 nums[0…4] 全部加 2,再给…,最后nums
数组的值是什么?
常规的思路是模拟所有操作,但是效率会很低。这时候可以使用差分数组来优化。类似前缀和数组的构造,我们先对nums
数组来构造一个diff
差分数组,diff[i]
就是nums[i]
和nums[i - 1]
之差
std::vector<int> diff(nums.size());
// 构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.size(); i++) {
diff[i] = nums[i] - nums[i - 1];
}
通过这个 diff 差分数组是可以反推出原始数组 nums 的,代码逻辑如下:
std::vector<int> res(nums.size());
res[0] = diff[0];
for (int i = 1; i < nums.size(); i++) {
res[i] = res[i - 1] + diff[i];
}
问:如果我们想要对区间进行增减应该怎么做呢?
- 如果你想对区间 nums[i…j] 的元素全部加 3,那么只需要让 diff[i] += 3,然后再让 diff[j+1] -= 3 即可
- 原理很简单,回想 diff 数组反推 nums 数组的过程,diff[i] += 3 意味着给 nums[i…] 所有的元素都加了 3,然后 diff[j+1] -= 3 又意味着对于 nums[j+1…] 所有元素再减 3,那综合起来,是不是就是对 nums[i…j] 中的所有元素都加 3 了?
只要花费 O(1) 的时间修改 diff 数组,就相当于给 nums 的整个区间做了修改。多次修改 diff,然后通过 diff 数组反推,即可得到 nums 修改后的结果。
实现:
class Difference{
std::vector<int> diff;
public:
/* 输入一个初始数组,区间操作将在这个数组上进行 */
Difference(std::vector<int> &nums){
assert(!nums.empty());
diff.resize(nums.size());
// 根据初始数组构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.size(); ++i) {
diff[i] = nums[i] - nums[i - 1];
}
}
/* 给闭区间 [i, j] 增加 val(可以是负数)*/
void increment(int i, int j, int val){
assert(i < diff.size());
diff[i] += val;
if(j + 1 < diff.size()){
diff[j + 1] -= val;
}
}
/* 返回结果数组 */
std::vector<int> result(){
std::vector<int> res(diff.size());
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.size(); ++i) {
res[i] = diff[i] + res[i - 1];
}
return res;
}
};