一维差分

差分

输入一个长度为 n 的整数序列。

接下来输入 m 个操作,每个操作包含三个整数 l, r, c,表示将序列中 [l, r] 之间的每个数加上 c

请你输出进行完所有操作后的序列。

所用方法和基本原理

  1. 差分原理
    • 差分是前缀和的逆运算。对于给定的数组 a,其差分数组 b 满足 b[i] = a[i] - a[i - 1]i > 0),b[0] = a[0]
    • 对差分数组 b[l, r] 区间进行操作,即 b[l] += cb[r + 1] -= c,这等价于对原数组 a[l, r] 区间内的每个元素都加上 c。这是因为,当对差分数组进行这样的操作后,通过前缀和还原原数组时,[l, r] 区间内由于 b[l] 的增加,使得 a[i]l <= i <= r)会增加 c,而 r + 1 及之后的元素会因为 b[r + 1] -= c 抵消掉多增加的部分。
  2. 具体实现
    • insert 方法用于对差分数组进行上述的区间操作。通过 b[l] += cb[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. 初始化差分数组
    • 输入序列 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] += 1b[2] += 2b[3] += 3b[4] -= 3)。
  2. 进行操作
    • 第一个操作 l = 1r = 2c = 2,执行 insert(1, 2, 2),差分数组变为 [1 + 2, 2 + 2, 3, -3 - 2] = [3, 4, 3, -5]
    • 第二个操作 l = 2r = 3c = 1,执行 insert(2, 3, 1),差分数组变为 [3, 4 + 1, 3 + 1, -5 - 1] = [3, 5, 4, -6]
  3. 还原数组
    • 通过 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]

方法的优劣

  1. 时间复杂度
    • 初始化阶段:初始化差分数组的时间复杂度为 (O(n)),因为要对原数组的每个元素进行一次 insert 操作。
    • 操作阶段:进行 m 次区间操作,每次操作时间复杂度为 (O(1)),所以这部分时间复杂度为 (O(m))。
    • 还原阶段:通过前缀和还原数组的时间复杂度为 (O(n))。
    • 总体时间复杂度为 (O(n + m))。相比于每次操作都遍历 [l, r] 区间的暴力解法(时间复杂度为 (O(m \times n))),当 mn 较大时,差分方法效率更高。
  2. 空间复杂度
    • 需要额外的空间来存储差分数组 b,空间复杂度为 (O(n)),因为差分数组的长度与原数组长度相同(这里假设最大长度为100000)。

优点

  • 对于频繁的区间操作,时间复杂度低,效率高。
  • 实现相对简单,易于理解和编码。

缺点

  • 需要额外的空间来存储差分数组,当原数组非常大时,会占用较多内存。
  • 仅适用于区间修改、单点查询的场景,如果需要进行区间查询等其他操作,可能需要结合其他数据结构。
posted @ 2025-07-03 17:52  起个数先  阅读(41)  评论(0)    收藏  举报