差分数组(Difference Array)是一种用于高效处理数组区间增量操作 的数据结构或技巧。它的核心思想是将原始数组的区间修改操作转换为差分数组的端点修改操作,从而将时间复杂度从 O(n) 降低到 O(1)。
核心概念
-
差分数组
diff
的定义:-
给定一个原始数组
arr
,长度为n
。 -
差分数组
diff
的长度也为n
。 -
diff[0] = arr[0]
(第一个元素) -
对于
i >= 1
:diff[i] = arr[i] - arr[i - 1]
(当前元素减去前一个元素)原始数组 arr: [a0, a1, a2, a3, ..., a{n-1}]
差分数组 diff:
diff[0] = a0
diff[1] = a1 - a0
diff[2] = a2 - a1
...
diff[i] = a{i} - a{i-1}
...
diff[n-1] = a{n-1} - a{n-2}
-
-
从差分数组还原原始数组:
-
差分数组
diff
存储了相邻元素的差值。 -
通过对
diff
数组求前缀和 ,可以还原出原始数组arr
:-
arr[0] = diff[0]
-
arr[1] = diff[0] + diff[1]
-
arr[2] = diff[0] + diff[1] + diff[2]
-
...
-
arr[i] = diff[0] + diff[1] + ... + diff[i] = arr[i-1] + diff[i]
(对于i >= 1
)arr[0] = diff[0]
arr[1] = diff[0] + diff[1] = arr[0] + diff[1]
arr[2] = diff[0] + diff[1] + diff[2] = arr[1] + diff[2]
...
arr[i] = arr[i-1] + diff[i]
-
-
为什么有用?区间修改操作
差分数组最大的威力在于处理**对原始数组某个区间内所有元素同时加上(或减去)一个常数 k
** 的操作。
-
原始方法: 如果要给
arr
的区间[l, r]
(包含l
和r
)的每个元素都加上k
,需要遍历该区间内的所有元素(l
到r
),时间复杂度为 O(n)(n 是区间长度)。 -
差分数组方法: 只需要修改差分数组
diff
的两个元素:-
diff[l] += k
-
if (r + 1 < n) diff[r + 1] -= k
(如果r + 1
没有超出数组边界)
-
原理:
-
修改
diff[l] += k
:根据还原公式arr[i] = arr[i-1] + diff[i]
,这个操作会导致从位置l
开始,之后所有位置 的arr[i]
在计算时都额外加上了k
(因为diff[l]
变大了k
,这个k
会传递到arr[l]
,arr[l+1]
,arr[l+2]
, ...)。 -
修改
diff[r + 1] -= k
(如果r + 1 < n
):这个操作是为了抵消掉区间[r+1, n-1]
上额外加上的k
。当还原到arr[r+1]
时,diff[r+1]
减少了k
,使得arr[r+1] = arr[r] + (diff[r+1] - k)
。由于arr[r]
已经被加了k
(来自第一步的影响),arr[r+1]
的计算变成了(arr[r] + k) + (diff[r+1] - k) = arr[r] + diff[r+1]
,正好是原始值加上diff[r+1]
,消除了k
的影响。之后的位置同理。
结果: 只有区间 [l, r]
内的元素在还原时被加上了 k
,区间外的元素不受影响。
操作步骤总结
-
初始化: 根据原始数组
arr
构造差分数组diff
。 -
区间修改: 当需要对
arr
的区间[l, r]
增加k
时:-
diff[l] += k
-
if (r + 1 < n) diff[r + 1] -= k
-
(时间复杂度 O(1))
-
-
查询(还原): 当需要知道修改后的
arr[i]
的值时,或者需要得到整个修改后的数组时,对差分数组diff
求前缀和得到arr
。- (查询单个元素 O(1) - 如果维护了前缀和数组;还原整个数组 O(n))
优点
-
区间修改效率高: 将 O(n) 的区间修改操作降低到 O(1)。
-
实现简单: 概念清晰,代码实现容易。
缺点
- 单点或区间查询效率低: 查询单个元素或区间和需要 O(n) 时间还原前缀和(除非额外维护前缀和数组)。如果查询非常频繁,可能需要结合树状数组或线段树等更高级的数据结构。
应用场景
差分数组特别适用于以下情况:
-
大量区间修改操作: 问题需要对数组进行多次区间增减操作。
-
最后统一查询: 所有修改操作完成后,才需要查询最终结果(只需还原一次)。
-
离线处理: 可以预先记录所有修改操作,最后一次性应用差分并还原。
典型应用:
-
航班预订统计(LeetCode 1109)
-
区间加法(LeetCode 370)
-
拼车(LeetCode 1094)
-
处理日程安排、资源分配中的时间段重叠或计数问题。
-
图像处理中的某些滤镜效果(计算量大时)。
示例
原始数组: arr = [1, 3, 5, 2, 4]
构造差分数组:
-
diff[0] = arr[0] = 1
-
diff[1] = arr[1] - arr[0] = 3 - 1 = 2
-
diff[2] = arr[2] - arr[1] = 5 - 3 = 2
-
diff[3] = arr[3] - arr[2] = 2 - 5 = -3
-
diff[4] = arr[4] - arr[3] = 4 - 2 = 2
-
diff = [1, 2, 2, -3, 2]
操作: 给区间 [1, 3]
(即第2个到第4个元素,值 3, 5, 2
)加 5
。
-
修改差分数组:
-
l = 1
:diff[1] += 5
->diff[1] = 2 + 5 = 7
-
r + 1 = 4
(4 < 5
):diff[4] -= 5
->diff[4] = 2 - 5 = -3
-
新
diff = [1, 7, 2, -3, -3]
-
还原修改后的数组 arr'
:
-
arr'[0] = diff[0] = 1
-
arr'[1] = arr'[0] + diff[1] = 1 + 7 = 8
-
arr'[2] = arr'[1] + diff[2] = 8 + 2 = 10
-
arr'[3] = arr'[2] + diff[3] = 10 + (-3) = 7
-
arr'[4] = arr'[3] + diff[4] = 7 + (-3) = 4
-
arr' = [1, 8, 10, 7, 4]
(验证:原区间[1, 3]
即3, 5, 2
变成了8, 10, 7
,都加了5
)
总结
差分数组是一种通过存储相邻元素差值来优化数组区间修改操作的技术。它通过将区间修改转化为两个端点的常数时间修改,极大地提高了处理大量区间增减操作的效率,尤其适用于修改操作频繁而查询操作较少或最后才进行的场景。理解差分数组的构建、修改和还原过程是掌握这一技巧的关键。