一、差分定义
对于一个数组a[],其差分数组diff[]的表示为:diff[i] = a[i] - a[i-1]
,其中,i从1开始,diff[1] = a[1],数组的下标也从1开始。
从这个定义中,我们可以得到差分数组的一个性质:对差分数组求前缀和,可以还原原数组,这里我们用一个简单的过程来描述。
diff[1] + diff[2]+...+diff[i]
=a[1] + (a[2]-a[1])+...+(a[i]-a[i-1])
=a[i]
二、常规对于区间修改问题的处理
假定我们有一个数组a[],我们现在的任务是将其[l,r]的区间上所有数都加上值C,并输出这个数组,常规的做法是:
-
遍历数组区间
-
将所有的数值都加C
-
输出数组
如果我们只对它的这一个区间进行操作,这样做没有什么问题,但是如果对数组a[],我们不只要进行从区间[l,r]上的操作,还要在区间[p,q],[m,n]...好多个区间上进行操作,我们要怎么办?
-
遍历第一个区间
-
将所有的数值都进行操作
-
遍历第二个区间
-
将所有的数值都进行操作
-
....(重复操作)
-
输出数组
可见这个过程是十分繁琐的,假如我们有特别多的区间,运行时间会过长,这个时候我们就要运用差分数组了。
那么我们应该怎么运用差分数组呢?我们首先思考,我想对区间[l,r]进行加上一个数值C,那么我的差分数组应该怎么办呢?
三、差分数组的区间操作
我们希望通过差分数组来优化区间加法操作。那么,当我们想对区间[l, r]上的所有元素加上一个值C时,应该如何修改差分数组呢?
我们回顾一下差分数组的定义:diff[i] = a[i] - a[i-1]
。这个定义告诉我们,差分数组的每一个元素代表了原数组中相邻元素的差值。那么,当我们想对区间[l, r]进行加C操作时,我们只需要在差分数组中做两步修改:
-
在索引l的位置增加C:这是因为我们希望从位置l开始,原数组的所有元素都增加C,这会在差分数组中反映为
diff[l] += C
。例如我们a[l]是5,a[l-1]是3,我们现在让a[l]加上一个C,原本的diff[l]=a[l]-a[l-1]=3
,而现在就是diff[l]=a[l]-a[l-1]=5+C-3=2+C
,也就有了diff[l] += C
-
在索引r+1的位置减少C:我们希望加C的操作只在区间[l, r]内生效,所以我们需要在r+1的位置将差分值减去C,这样从r+1开始,后续的元素就不会再受此操作影响。具体操作为
diff[r+1] -= C
。分析过程和索引l处一致。 -
中间部分:中间部分当然是不变的啦。
通过这种方式,我们在差分数组中只做了两次修改,而不需要遍历区间内的每个元素,这大大提高了效率。
举个栗子:
假设原数组 a[]
是这样的:
a[] = [1, 2, 3, 4, 5]
我们希望进行以下几个区间操作:
-
对区间
[2, 4]
中的元素加上C = 3
-
对区间
[1, 3]
中的元素加上C = 2
-
对区间
[4, 5]
中的元素加上C = 1
首先,我们根据原数组计算出差分数组 diff[]
:
a[] = [1, 2, 3, 4, 5] diff[] = [1, 1, 1, 1, 1]
对区间 [2, 4]
加上 C = 3
为了对区间 [2, 4]
中的元素加上 3
,我们对差分数组进行如下修改:
-
diff[2] += 3
(在索引 2 开始加 3) -
diff[5] -= 3
(在索引 5 结束,索引 5 之后不再加 3)
更新后的差分数组:
diff[] = [1, 4, 1, 1, -3]
对区间 [1, 3]
加上 C = 2
接下来,对区间 [1, 3]
中的元素加上 2
,我们对差分数组进行如下修改:
-
diff[1] += 2
(在索引 1 开始加 2) -
diff[4] -= 2
(在索引 4 结束,索引 4 之后不再加 2)
更新后的差分数组:
diff[] = [3, 4, 1, -1, -3]
对区间 [4, 5]
加上 C = 1
最后,对区间 [4, 5]
中的元素加上 1
,我们对差分数组进行如下修改:
-
diff[4] += 1
(在索引 4 开始加 1) -
diff[6] -= 1
(在索引 6 结束,索引 6 之后不再加 1)
更新后的差分数组:
diff[] = [3, 4, 1, 0, -3, -1]
还原原数组
现在,我们可以通过前缀和计算来还原更新后的原数组:
-
a[1] = diff[1] = 3
-
a[2] = a[1] + diff[2] = 3 + 4 = 7
-
a[3] = a[2] + diff[3] = 7 + 1 = 8
-
a[4] = a[3] + diff[4] = 8 + 0 = 8
-
a[5] = a[4] + diff[5] = 8 + (-3) = 5
-
a[6] = a[5] + diff[6] = 5 + (-1) = 4(其实这个元素不存在)
所以,更新后的数组 a[]
为:
a[] = [3, 7, 8, 8, 5]
所以,通过差分数组,我们成功地在多个区间上执行了加法操作,并且每次操作只需要在差分数组中进行两次修改。最终,通过一次前缀和计算,我们还原了更新后的数组,且时间复杂度得到了显著优化。
四、实现代码
#获取差分数组
def get_diff(a):
n = len(a)
diff = [0] * (n + 1)
diff[0] = a[0]
for i in range(1, n):
diff[i] = a[i] - a[i - 1]
return diff
#实现对l~r的区间进行加上或减去一个数
def change_diff(l, r, z, diff):
# 需要注意,x,y 给的是第几个元素,并非下标
diff[l - 1] = diff[l - 1] + z
diff[r] = diff[r] - z # diff数组多加上一位是为了避免这里不越界
return diff
在这里需要注意的是,在创建diff数组的时候采取多加了一维,这是为了保证右边界不会越界。
五、例题
具体在什么时候会用到差分?笔者总结了这两点:
-
对一个数组的某个连续区间的元素统一进行操作
-
这样的连续区间有多个