今天学到了一个新知识,差分数组,参考灵神笔记。
举例
考虑数组
a
=
[
1
,
3
,
3
,
5
,
8
]
\ a = [1,3,3,5,8]
a=[1,3,3,5,8],对其中的相邻元素两两作差(右边减左边),得到数组
[
2
,
0
,
2
,
3
]
\ [2,0,2,3]
[2,0,2,3]。然后在开头补上
a
[
0
]
\ a[0]
a[0],得到差分数组:
d
=
[
1
,
2
,
0
,
2
,
3
]
d = [1,2,0,2,3]
d=[1,2,0,2,3]
这有什么用呢?如果从左到右累加
d
\ d
d 中的元素,我们就「还原」回了
a
\ a
a 数组
[
1
,
3
,
3
,
5
,
8
]
\ [1,3,3,5,8]
[1,3,3,5,8]。
这又有什么用呢?现在把连续子数组
a
[
1
]
,
a
[
2
]
,
a
[
3
]
\ a[1],a[2], a[3]
a[1],a[2],a[3] 都加上 10,得到
a
′
=
[
1
,
13
,
13
,
15
,
8
]
\ a^{\prime}=[1,13,13,15,8]
a′=[1,13,13,15,8]。再次两两作差,并在开头补上
a
′
[
0
]
\ a^{\prime}[0]
a′[0],得到差分数组:
d
′
=
[
1
,
12
,
0
,
2
,
−
7
]
d^{\prime} = [1,12,0,2,-7]
d′=[1,12,0,2,−7]
对比
d
\ d
d 和
d
′
\ d^{\prime}
d′,你会发现,对
a
\ a
a 中连续子数组的操作,可以转变成对差分数组
d
\ d
d 中两个数的操作。
定义和性质
对于数组
a
\ a
a,定义其差分数组(difference array)为
d
[
i
]
=
{
a
[
0
]
,
i
=
0
a
[
i
]
−
a
[
i
−
1
]
,
i
≥
1
d[i] = \begin{cases} a[0],&i=0 \\ a[i] - a[i - 1],&i\geq1 \end{cases}
d[i]={a[0],a[i]−a[i−1],i=0i≥1
性质1:从左到右累加
d
\ d
d 中的元素,可以得到数组
a
\ a
a。
性质2:如下两个操作是等价的。
- 区间操作:把 a \ a a 的子数组 a [ i ] , a [ i + 1 ] , ⋅ ⋅ ⋅ , a [ j ] \ a[i],a[i+1],···,a[j] a[i],a[i+1],⋅⋅⋅,a[j] 都加上 x \ x x。
- 单点操作:把 d [ i ] \ d[i] d[i] 增加 x \ x x,把 d [ j + 1 ] \ d[j+1] d[j+1] 减少 x \ x x。特别的,如果 j + 1 = n \ j+1=n j+1=n,则只需把 d [ i ] \ d[i] d[i] 增加 x \ x x。( n \ n n 作为数组 a \ a a 的长度)
利用性质 2,我们只需要 O ( 1 ) \ O(1) O(1) 的时间就可以完成数组 a \ a a 上的区间操作。最后利用性质 1 从差分数组复原出数组 a \ a a。
JS代码模版
// 你有一个长为 n 的数组 a,一开始所有元素均为 0。
// 给定一些区间操作,其中 queries[i] = [left, right, x],
// 你需要把子数组 a[left], a[left+1], ... a[right] 都加上 x。
// 返回所有操作执行完后的数组 a。
function solve(n, queries) {
let diff = new Array(n).fill(0); // 差分数组
for (const q of queries) {
const left = q[0], right = q[1], x = q[2];
diff[left] += x;
if (right + 1 < n) {
diff[right + 1] -= x;
}
}
for (let i = 1; i < n; i++) {
diff[i] += diff[i - 1]; // 直接在差分数组上复原数组 a
}
return diff;
}
第一次手打这些数学公式,还有点累的啊~