大家在看我这篇文章之前可以先看一下前缀和的概念
我们先了解一下差分数组解决什么问题:当你有一个数组,你想要进行m次询问,每次询问为在区间内的每一位加上或减去一个值(即[l, r] + value或[l, r] - value),返回最终询问过后的数组,可能有点抽象,那就举个例子进行理解吧。
首先有一个数组,还按之前的前缀和的数组来说吧,arr = [1, 5, 8, 2, 13, 6],我们对其进行3次询问,第一次询问是在下标为[1, 4]的区间内的每个值+5(结果是arr = [1, 10, 13, 7, 18, 6]),第二次询问是在下标为[3, 5]的区间内的每个值+1(结果是arr = [1, 10, 13, 8, 19, 7]),第三次询问是在下标为[0, 2]的区间内的每个值-3(结果是arr = [-2, 7, 10, 8, 19, 7]),我们首先想到的无非就是暴力做法,也就是对每次询问都对询问内的区间进行枚举,然后进行相应的加减运算,当然,询问次数少完全可以这样做,但如果对其询问的次数多了呢?如果数组的长度为n, 进行m次询问的话,这样的最差时间复杂度为O(m * n),还是挺高的,容易造成超时,我们此刻就可以对其进行优化
我们观察一下解决的问题,因为我们最终想要得到的是一个更改后数组的结果,所以我们根本不用知道更改过程之间的数组内的每个值,因此就要引入差分数组
我们用D来表示差分数组,当I = 0时,D[i] = arr[i],当I > 0时,D[i] = arr[i] – arr[I – 1].根据这个公式,原先arr的差分数组可以表示为D = [1, 4, 3, -6, 11, -7],仔细观察不难发现,差分数组其实就是前缀和的逆运算,我们反观差分数组D,arr[i] = D[0] + D[1] + D[2]+……+D[i]。
我们利用的其实就是这个性质,先上公式:当我们想要修改数组中区间的时候,[l. r] + value 等价于D[l] + value, D[r + 1] – value。
我们对其仍然进行以上三次操作,第一次询问[1, 4]结果为D = [1, 9, 3, -6, 11, -12],第二次询问[3, 5],诶?此刻不难发现,在D[r + 1]上数组越界了,怎么办?其实在这里有两种解决方案,一是再额外对差分数组开辟一块内存空间来存放-value,二是不用管他,不写D[r + 1]即可,也就是在这里我们写成D = [1, 9, 3, -5, 11, -12, -1]和D = [1, 9, 3, -5, 11, -12]均可,自己可以自行实现一下证明一下是否一样,这里我们采用第二种写法,第三次询问[0, 2]的结果为D = [-2, 9, 3, -2, 11, -12],最终的差分数组修改完了,在前面我们知道差分数组就是原数组的前缀和的逆运算,我们对其求每一项的前缀和得到最终的数组D = [-2, 7, 10, 8, 19, 7]可以看到和上文通过枚举求得的最终数组arr是一样的,这就是差分数组,时间复杂度为O(m + n),适合多次区间操作一次数组询问。
下面是代码模板:
#include<bits/stdc++.h>
using namespace std;
//定义差分数组,这里我们多定义一位,在程序上多消耗一个存储空间可以减少一次
//if语句的判断,来加快运行时间(判断临界的时候)
vector<int>D(7, 0);
void add(int l, int r, int val) {
D[l] += val;
D[r + 1] -= val;
}
int main()
{
vector<int>arr = {1, 5, 8, 2, 13, 6};
//对差分数组进行初始化
for (int i = 0; i < 6; i++) {
if (i == 0)
D[i] = arr[i];
else
D[i] = arr[i] - arr[i - 1];
}
//对区间进行加减操作
add(1, 4, 5);
add(3, 5, 1);
add(0, 2, -3);
//求得差分数组的每一项的前缀和
for (int i = 1; i < 6; i++) {
D[i] += D[i - 1];
}
//输出结果
for (int i = 0; i < 6; i++) {
cout << D[i] << " ";
}
system("pause");
return 0;
}