前缀和 & 差分 基于容斥思想。
所谓容斥,就是为了解决不重不漏的计算,容斥的做法是:
先不管重复全部计算,再逐个将重复计算的内容剔除。
举个简单的例子:已知宽高,求正方形的周长。
1、一维数组前缀和
已知数组 nums, 定义前缀和数组 sum,使得 i ∈ [ 0, num.length-1 ]
, 有 sum[i] 等于 nums中 0 到 i 的数据和。
/*
思路:
(1) sum[0] = nums[0]
(2) sum[i] = sum[i-1] + nums[i]
*/
function fn(nums) {
let sum = [];
sum[0] = nums[0];
for(let i=1;i<nums.length;i++) sum[i] = sum[i-1] + nums[i];
return sum;
}
// test case
let nums = [1,2,3,4,5];
let res = fn(nums);
console.log(res);
// [ 1, 3, 6, 10, 15 ]
应用,求区间和: 开区间、左开右闭、左闭右开、闭区间
下面以区间 [2, 4] 为例:
// (2,4) -> 4
console.log( sum[4-1] - sum[2] );
// (2,4] -> 9
console.log( sum[4] - sum[2] );
// [2, 4) -> 7
console.log( sum[4-1] - sum[2-1] );
// [2,4] -> 12
console.log( sum[4] - sum[2-1] );
2、二维数组前缀和
已知M*N矩阵 nums, 定义前缀数组 sum,使得
x ∈ [0, m) , y ∈ [0,n)
有
/*
思路:
(1) 在 0,0 处 sum[0][0] = nums[0][0]
(2) 在 x=0 处 sum[x][y] = sum[x][y-1] + nums[x][y]; 无需考虑容斥
(3) 在 y=0 处 sum[x][y] = sum[x-1][y] + nums[x][y]; 无需考虑容斥
(4) 其他 sum[x][y] = sum[x-1][y] + sum[x][y-1] + nums[x][y] - sum[x-1][y-1];
*/
function fn(nums) {
let m = nums.length;
let n = nums[0].length;
let sum = new Array(m).fill(0).map(v => new Array(n).fill(0));
for(let x=0;x<m;x++) {
for(let y=0;y<n;y++) {
if(x===0 && y===0) sum[x][y] = nums[x][y];
if(x===0 && y!==0) sum[x][y] = sum[x][y-1] + nums[x][y];
if(x!==0 && y===0) sum[x][y] = sum[x-1][y] + nums[x][y];
if(x!==0 && y!==0) sum[x][y] = sum[x-1][y] + sum[x][y-1] + nums[x][y] - sum[x-1][y-1];
}
}
return sum;
}
// test case
let nums =
[
[1,2,4,3],
[5,1,2,4],
[6,3,5,9]
];
let sum = fn(nums);
console.log(sum);
// [ [ 1, 3, 7, 10 ], [ 6, 9, 15, 22 ], [ 12, 18, 29, 45 ] ]
3、差分数组
差分就是求前缀和的逆运算,已知数组 nums,定义差分数组 diff,使得
i ∈ [ 0, num.length-1 ]
有
diff[0] = nums[0];
diff[i] = nums[i] - nums[i-1];
可知,有以下性质:
(1)nums[i] 是 diff[i] 的前缀和
(2)可以使用差分来计算nums每项的前缀和(难以理解,为什么要走弯路用差分去求原数组前缀和???先记下来吧)
由 nums 求差分数组 diff :
function fn(nums) {
let diff = [];
diff[0] = nums[0];
for(let i=1;i<nums.length;i++) diff[i] = nums[i] - nums[i-1];
return diff;
}
// test case:
let nums = [1,2,3,4,5];
let diff = fn(nums);
console.log(diff);
// [ 1, 1, 1, 1, 1 ]
应用:快速处理区间加减操作
第一行为 原数组 nums
第二行为 差分数组 diff
可以从上面的图示观察到,某段闭区间 [ L, R ]
中每个元素都加 k 的话
只需维护差分数组,diff[ L ] += k
、diff[ R+1 ] -= k
因此,到这里,其实我们就能明白为什么要走弯路用差分数组求原数组,用差分数组求前缀和了,毕竟我们在频繁的区间加减后,只维护了差分数组,而不维护数组。
示例: