前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。

  1. class PrefixSum {
  2. // 前缀和数组
  3. private int[] prefix;
  4. /* 输入一个数组,构造前缀和 */
  5. public PrefixSum(int[] nums) {
  6. prefix = new int[nums.length + 1];
  7. // 计算 nums 的累加和
  8. for (int i = 1; i < prefix.length; i++) {
  9. prefix[i] = prefix[i - 1] + nums[i - 1];
  10. }
  11. }
  12. /* 查询闭区间 [i, j] 的累加和 */
  13. public int query(int i, int j) {
  14. return prefix[j + 1] - prefix[i];
  15. }
  16. }

差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。
这里就需要差分数组的技巧,类似前缀和技巧构造的 prefix 数组,我们先对 nums 数组构造一个 diff 差分数组,diff[i] 就是 nums[i] 和 nums[i-1] 之差:

  1. int[] diff = new int[nums.length];
  2. // 构造差分数组
  3. diff[0] = nums[0];
  4. for (int i = 1; i < nums.length; i++) {
  5. diff[i] = nums[i] - nums[i - 1];
  6. }

image.png
根据公式,可以通过 diff 查分数组反推出原始数组 nums:

  1. int[] res = new int[diff.length];
  2. // 根据差分数组构造结果数组
  3. res[0] = diff[0];
  4. for (int i = 1; i < diff.length; i++) {
  5. res[i] = res[i - 1] + diff[i];
  6. }

这样构造差分数组 diff,就可以快速进行区间增减的操作,如果你想对区间 nums[i..j] 的元素全部加 3,那么只需要让 diff[i] += 3,然后再让 diff[j+1] -= 3 即可:
image.png
原理是这样的,回想diff数组反推nums数组的过程,diff[i] += 3 意味着给 nums[i..] 的所有元素都加了3,例如给上面的图中 diff[i] 加3变成0,那么nums[i] 也要加3成为8,后面 num[i+1] 为了保持diff[i+1]不变,需要也加3,所以diff[i] += 3 意味着给 nums[i..] 的所有元素都加了3。
然后 diff[j+1] -= 3 意味着对于 nums[j+1..] 所有元素再减3,那么综合起来,就是对 nums[i..j] 中的所有元素都加3了。
现在我们把差分数组抽象成一个类,包含 increment 方法和 result 方法:

差分数组工具类

这里需要注意 increment函数中的if语句,当 j + 1 >= diff.length 时,说明是对nums[i..]所有的元素进行修改,不需要再给diff数组减val了:

  1. // 差分数组工具类
  2. class Difference {
  3. // 差分数组
  4. private int[] diff;
  5. /* 输入一个初始数组,区间操作将在这个数组上进行 */
  6. public Difference(int[] nums) {
  7. assert nums.length > 0;
  8. diff = new int[nums.length];
  9. // 根据初始数组构造差分数组
  10. diff[0] = nums[0];
  11. for (int i = 1; i < nums.length; i++) {
  12. diff[i] = nums[i] - nums[i - 1];
  13. }
  14. }
  15. /* 给闭区间 [i, j] 增加 val(可以是负数)*/
  16. public void increment(int i, int j, int val) {
  17. diff[i] += val;
  18. if (j + 1 < diff.length) {
  19. diff[j + 1] -= val;
  20. }
  21. }
  22. /* 返回结果数组 */
  23. public int[] result() {
  24. int[] res = new int[diff.length];
  25. // 根据差分数组构造结果数组
  26. res[0] = diff[0];
  27. for (int i = 1; i < diff.length; i++) {
  28. res[i] = res[i - 1] + diff[i];
  29. }
  30. return res;
  31. }
  32. }

370. 区间加法🔒

image.png
Difference类就可以解决:

  1. int[] getModifiedArray(int length, int[][] updates) {
  2. // nums 初始化为全 0
  3. int[] nums = new int[length];
  4. // 构造差分解法
  5. Difference df = new Difference(nums);
  6. for (int[] update : updates) {
  7. int i = update[0];
  8. int j = update[1];
  9. int val = update[2];
  10. df.increment(i, j, val);
  11. }
  12. return df.result();
  13. }

1109. 航班预订统计

这道题,表面看起来很复杂,题目读明白才知道本质是一道差分数组的题。这道题需要注意的是,输入中给出的索引需要减1,因为输入里是从1开始算的。

  1. int[] corpFlightBookings(int[][] bookings, int n) {
  2. // nums 初始化为全 0
  3. int[] nums = new int[n];
  4. // 构造差分解法
  5. Difference df = new Difference(nums);
  6. for (int[] booking : bookings) {
  7. // 注意转成数组索引要减一哦
  8. int i = booking[0] - 1;
  9. int j = booking[1] - 1;
  10. int val = booking[2];
  11. // 对区间 nums[i..j] 增加 val
  12. df.increment(i, j, val);
  13. }
  14. // 返回最终的结果数组
  15. return df.result();
  16. }

1094. 拼车

这道题也是用差分数组,但是差分数组的长度(车站的个数)没有给出,所以取数据的取值范围1001.

boolean carPooling(int[][] trips, int capacity) {
    // 最多有 1001 个车站
    int[] nums = new int[1001];
    // 构造差分解法
    Difference df = new Difference(nums);

    for (int[] trip : trips) {
        // 乘客数量
        int val = trip[0];
        // 第 trip[1] 站乘客上车
        int i = trip[1];
        // 第 trip[2] 站乘客已经下车,
        // 即乘客在车上的区间是 [trip[1], trip[2] - 1]
        int j = trip[2] - 1;
        // 进行区间操作
        df.increment(i, j, val);
    }

    int[] res = df.result();

    // 客车自始至终都不应该超载
    for (int i = 0; i < res.length; i++) {
        if (capacity < res[i]) {
            return false;
        }
    }
    return true;
}