一维差分
差分
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l, r, c,表示将序列中 [l, r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
所用方法和基本原理
- 差分原理:
- 差分是前缀和的逆运算。对于给定的数组
a,其差分数组b满足b[i] = a[i] - a[i - 1](i > 0),b[0] = a[0]。 - 对差分数组
b的[l, r]区间进行操作,即b[l] += c,b[r + 1] -= c,这等价于对原数组a的[l, r]区间内的每个元素都加上c。这是因为,当对差分数组进行这样的操作后,通过前缀和还原原数组时,[l, r]区间内由于b[l]的增加,使得a[i](l <= i <= r)会增加c,而r + 1及之后的元素会因为b[r + 1] -= c抵消掉多增加的部分。
- 差分是前缀和的逆运算。对于给定的数组
- 具体实现:
insert方法用于对差分数组进行上述的区间操作。通过b[l] += c和b[r + 1] -= c来标记对原数组[l, r]区间的修改。prefixSum方法通过对差分数组求前缀和来还原出经过所有操作后的原数组。从i = 1开始,a[i] = a[i - 1] + b[i],逐步累加得到最终的数组。
代码及注释
import java.util.Scanner;
public class Difference {
// 定义差分数组b,这里假设最大长度为100000
public static int[] b = new int[100000];
// 插入操作,对差分数组进行修改
public static void insert(int l, int r, int c) {
b[l] += c;
b[r + 1] -= c;
}
// 通过差分数组求前缀和还原出最终的数组
public static int[] prefixSum(int[] b) {
int[] a = new int[b.length];
for (int i = 1; i < b.length; i++) {
a[i] = a[i - 1] + b[i];
}
return a;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
// 初始化差分数组,将原数组的每个元素看作是长度为1的区间插入
for (int i = 1; i < n + 1; i++) {
int tmp = sc.nextInt();
insert(i, i, tmp);
}
// 进行m次操作
for (int i = 0; i < m; i++) {
int l = sc.nextInt();
int r = sc.nextInt();
int c = sc.nextInt();
insert(l, r, c);
}
// 通过差分数组还原出最终的数组
int[] a = prefixSum(b);
// 输出最终的数组
for (int i = 1; i <= n; i++) {
System.out.print(a[i] + " ");
}
}
}
举例说明
假设输入的整数序列为 [1, 2, 3],即 n = 3,有 m = 2 个操作。
- 初始化差分数组:
- 输入序列
1, 2, 3,通过insert(i, i, tmp)操作,等价于insert(1, 1, 1),insert(2, 2, 2),insert(3, 3, 3)。 - 此时差分数组
b变为[1, 2, 3, -3](因为b[1] += 1,b[2] += 2,b[3] += 3,b[4] -= 3)。
- 输入序列
- 进行操作:
- 第一个操作
l = 1,r = 2,c = 2,执行insert(1, 2, 2),差分数组变为[1 + 2, 2 + 2, 3, -3 - 2] = [3, 4, 3, -5]。 - 第二个操作
l = 2,r = 3,c = 1,执行insert(2, 3, 1),差分数组变为[3, 4 + 1, 3 + 1, -5 - 1] = [3, 5, 4, -6]。
- 第一个操作
- 还原数组:
- 通过
prefixSum方法还原数组。 a[1] = a[0] + b[1] = 0 + 3 = 3。a[2] = a[1] + b[2] = 3 + 5 = 8。a[3] = a[2] + b[3] = 8 + 4 = 12。- 最终输出的数组为
[3, 8, 12]。
- 通过
方法的优劣
- 时间复杂度:
- 初始化阶段:初始化差分数组的时间复杂度为 (O(n)),因为要对原数组的每个元素进行一次
insert操作。 - 操作阶段:进行
m次区间操作,每次操作时间复杂度为 (O(1)),所以这部分时间复杂度为 (O(m))。 - 还原阶段:通过前缀和还原数组的时间复杂度为 (O(n))。
- 总体时间复杂度为 (O(n + m))。相比于每次操作都遍历
[l, r]区间的暴力解法(时间复杂度为 (O(m \times n))),当m和n较大时,差分方法效率更高。
- 初始化阶段:初始化差分数组的时间复杂度为 (O(n)),因为要对原数组的每个元素进行一次
- 空间复杂度:
- 需要额外的空间来存储差分数组
b,空间复杂度为 (O(n)),因为差分数组的长度与原数组长度相同(这里假设最大长度为100000)。
- 需要额外的空间来存储差分数组
优点:
- 对于频繁的区间操作,时间复杂度低,效率高。
- 实现相对简单,易于理解和编码。
缺点:
- 需要额外的空间来存储差分数组,当原数组非常大时,会占用较多内存。
- 仅适用于区间修改、单点查询的场景,如果需要进行区间查询等其他操作,可能需要结合其他数据结构。

浙公网安备 33010602011771号