目录
一、前言
在当今的算法与数据处理领域中,区间操作问题可谓是屡见不鲜。诸如在算法竞赛领域,给定一个整数数组,要求对数组中多个指定区间内的元素都增加或减少一个固定值,之后查询数组中所有元素的最大值、最小值或者判断是否满足某种特定的条件。
面对这类区间操作问题,如果采用常规的、逐个元素去更新的方式,那么当数据量庞大或者区间操作频繁时,无疑会消耗大量的时间和计算资源,效率极其低下。
而差分数组却能够非常高效地对原始数组的连续区间进行增减操作。在处理大量区间增减值的问题时,差分数组能够大幅度减少计算量,尤其是在数据量大、修改操作频繁的情况下。
二、差分数组的基本概念
差分数组(Difference Array)是一种用于高效处理区间更新问题的数据结构。它主要应用于需要对数组中某个区间内的元素进行频繁更新,并且最后一次性查询结果的场景。
对于一个给定的数组arr,设其存在相应的差分数组diff,diff需满足以下条件:
* diff.length() = arr.length()
* diff[0] = arr[0]
* diff[i] = arr[i] - arr[i-1] ( i > 0 )
即diff[i]表示arr[i]比arr[i-1]大多少(可以是负数),所以arr[i-1]+diff[i]可以得到arr[i]。我们总是维护一个对数组arr正确的diff,并且diff[0]一直与arr[0]相等,因此任意时刻可以复原 arr数组。
三、差分数组的基本操作
1.差分数组的创建
对数组arr,创建差分数组diff只需两步:
* 令diff[0] = arr[0]
* 令diff[i] = arr[i] - arr[i-1],i从1开始
时间复杂度位O(n),其中n为数组的长度。
#include <vector>
//初始化差分数组
std::vector<int> Initiate(std::vector<int> arr, int n)
{
std::vector<int> diff(n);
diff[0] = arr[0];
for (int i = 1; i < n ; i++)
{
diff[i] = arr[i] - arr[i-1];
}
return diff;
}
2.差分数组的更新
差分数组的区间修改(即更新操作)是差分数组的精髓所在,常规数组需采用逐个元素更新的方式去处理区间操作问题,假设数组长度为n,每次进行区间更新操作都需要遍历该区间内的每一个元素,需要O(n)的时间,而如果进行n次这样的操作,那么在最坏的情况下(对整个数组进行区间操作)时间复杂度为O(n^2)。
而差分数组的区间修改却只需要两步操作,我们假设对区间[l,r](l从1开始)内的元素进行统一修改。
* 对区间左端点进行+操作。这意味着从位置l开始,后边的数都增加了
* 对区间右端点进行-操作,如果r是diff的右边界,则不需要进行-操作。这意味着从位置r+1开始,后边的数就不再增加了。
换而言之,在区间[l,r]内,数组差值未变,但是他们区间边界的左右差值发生了变化。
通过上述操作,我们在时间O(1)内进行了区间的变化操作,如果进行n次这样的操作,时间复杂度也仅为O(n)。
#include <vector>
//更新差分数组
std::vector<int> Update(std::vector<int> diff, int left, int right, int val)
{
diff[left-1] += val; //对左边界进行+操作
if(right < diff.size()) //如果区间操作范围涵盖右边界,则不需要对右边界进行-操作
diff[right] -= val; //对右边界进行-操作
return diff;
}
3.根据差分数组还原原数组
如果要还原arr的元素值,我们需要对diff进行前缀和的操作。具体来说:arr[i]=diff[0]+diff[1]+........+diff[i]。
#include <vector>
//根据差分数组还原原数组
std::vector<int> Restore(std::vector<int> diff, std::vector<int> arr)
{
arr[0] = diff[0];
for(int i = 1; i < diff.size(); i++)
{
arr[i] = arr[i-1] + diff[i];
}
return arr;
}
4.运行实例
对数组arr={1,2,3,4,5},我们希望将区间[1,3]内的数每个加10。
1、构建差分数组diff={1,1,1,1,1}。
2、区间[1,3]增加10,diff[0]=10, diff[3]=-9
3、通过差分数组前缀和恢复原数组,得到结果arr={11,12,13,4,5}
#include <iostream>
#include <vector>
#include "Initiate.h"
#include "Update.h"
#include "Restore.h"
int main()
{
int n;
std::vector<int> arr = {1, 2, 3, 4, 5};
n = arr.size();
std::vector<int> diff(n);
diff = Initiate(arr, n);
diff = Update(diff, 1, 3, 10);
arr = Restore(diff,arr);
for (int i = 0; i < n; i++)
{
std::cout << arr[i] << " ";
}
return 0;
}
四、复杂度分析总结
1、初始化操作(Initiate函数)
时间复杂度:O(n)
在Initiate函数中,我们需要遍历数组一次来计算差分数组。对于长度为n的数组,循环执行n次,操作时间复杂度为O(n)。
空间复杂度:O(n)
我们需要额外的长度为 n 的数组 diff 来存储差分数组,因此空间复杂度为 O(n)。
2、区间更新操作(Update函数)
时间复杂度:O(1)
在Update函数中,我们只需对差分数组的左右边界进行操作,这两个操作的时间复杂度都是O(1)。因此,区间更新的时间复杂度为O(1)。
空间复杂度:O(1)
Update函数只对现有的差分数组进行修改,无需额外的存储空间。
3、还原操作(Restore函数)
时间复杂度:O(n)
在 Restore 函数中,我们需要遍历差分数组一次来还原原数组。对于长度为 n 的差分数组,循环执行 n 次,每次操作的时间复杂度为 O(1)。因此,还原原数组操作的时间复杂度为 O(n)。
空间复杂度:O(1)
还原原数组操作只对现有的数组进行修改,不需要额外的存储空间,因此空间复杂为 O(1)。
4、差分数组的优势
不使用差分数组
如果不使用差分数组,每次进行区间更新操作时,需要遍历区间内的所有元素并进行修改。对于长度为 n 的数组,进行 m 次区间更新操作,每次区间更新的平均长度为 k,则总的时间复杂度为 O(m∗k)。
使用差分数组
使用差分数组时,初始化操作的时间复杂度为 O(n),每次区间更新操作的时间复杂度为 O(1),进行 m 次区间更新操作的总时间复杂度为 O(m),还原原数组操作的时间复杂度为 O(n)。因此,总的时间复杂度为 O(n+m)。
优势对比
通过上述复杂度对比可以看出,当需要进行多次区间更新操作时,使用差分数组可以显著降低时间复杂度。特别是当区间更新的次数 m 较大,且区间长度 k 也较大时,差分数组的优势更加明显。例如,在一个长度为 1000 的数组中进行 100 次区间更新操作,每次区间长度为 100,如果不使用差分数组,时间复杂度为 O(100∗100)=O(10000);而使用差分数组,时间复杂度为O(1000+100)=O(1100),大大提高了效率。
综上所述,差分数组在处理区间操作问题时,通过将区间更新的时间复杂度从 O(k) 降低到 O(1),从而在整体上提高了算法的效率,尤其适用于需要进行多次区间更新的场景。
五、例题
示例代码
class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n)
{
vector<int> diff(n);
for (int i = 0 ; i < bookings.size(); i++)
{
diff[bookings[i][0]-1] += bookings[i][2];
if (bookings[i][1] < n)
diff[bookings[i][1]] -= bookings[i][2];
}
for (int i = 1 ; i < n ; i++)
{
diff[i] = diff[i] + diff[i-1];
}
return diff;
}
};
总结
差分数组适合在一个数组中连续区域内进行多次大量的相同数据的改变。
5105





