算法模式:差分数组

算法模式:差分数组

Christopher Alexander 在 《建筑的永恒之道》 中说:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。”受此影响,GoF 总结经验,写出了著名的 《设计模式》

在算法中,也有很多类似设计模式这样的解决方案。D瓜哥称其为“算法模式”。后面,慢慢写文章一一介绍一下。由浅及深,今天先来介绍最简单的一个模式:差分数组。

差分数组

差分数组:差分数组就是原始数组相邻元素之间的差。举例如下:

下标012345

原始数组

5

9

2

6

5

3

差分数组

5

4

-7

4

-1

-2

差分数组是从原始数组构造出来的一个辅助数组,表示相邻元素直接的差值。可用于解决需要对数组一个区间内同时做加减的操作。比如:随着公交站各个站台上下车,判断公交车是否超载。

LeetCode 370. 区间加法

假设你有一个长度为 n 的数组,初始情况下所有的数字均为 0,你将会被给出 k 个更新的操作。

其中,每个操作会被表示为一个三元组:[startIndex, endIndex, inc],表示需要将子数组 A[startIndex …​ endIndex](含 startIndexendIndex)增加 inc

请你返回 k 次操作后的数组。

示例:

输入: length = 5, updates = [[1,3,2],[2,4,3],[0,2,-2]]
输出: [-2,0,3,5,3]

解释:

初始状态:
[0,0,0,0,0]

进行了操作 [1,3,2] 后的状态:
[0,2,2,2,0]

进行了操作 [2,4,3] 后的状态:
[0,2,5,5,3]

进行了操作 [0,2,-2] 后的状态:
[-2,0,3,5,3]

思路分析

构造差分数组,把变量记录在差分数组上进行打标,全部打标完成后,再从第二项开始逐项求与前一项的累加值。

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2025-02-26 19:42:50
 */
public int[] getModifiedArray(int length, int[][] updates) {
  if (updates == null || updates.length == 0) {
    return new int[length];
  }
  // 数组元素初始即为 0,差值也是 0,则无需多余计算差分数组
  int[] diff = new int[length];

  for (int[] update : updates) {
    int start = update[0];
    int end = update[1];
    int val = update[2];
    diff[start] += val;
    if (end + 1 < diff.length) {
      diff[end + 1] -= val;
    }
  }

  for (int i = 1; i < length; i++) {
    diff[i] += diff[i - 1];
  }
  return diff;
}

LeetCode 1094. 拼车

车上最初有 capacity 个空座位。车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向

给定整数 capacity 和一个数组 tripstrip[i] = [numPassengers, from, to] 表示第 i 次旅行有 numPassengersi 乘客,接他们和放他们的位置分别是 fromitoi 。这些位置是从汽车的初始位置向东的公里数。

当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false

示例 1:

输入:trips = [[2,1,5],[3,3,7]], capacity = 4
输出:false

示例 2:

输入:trips = [[2,1,5],[3,3,7]], capacity = 5
输出:true

提示:

  • 1 <= trips.length <= 1000

  • trips[i].length == 3

  • 1 <= numPassengersi+ <= 100+

  • 0 <= fromi+ < to+i+ <= 1000+

  • 1 <= capacity <= 105

思路分析

这也是一个很典型的差分数组问题。题目是否可以接送所有乘客,就是判断在接送过程中是否超载。发现超载则为否,没有发现超载则可以。

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2024-07-05 15:01:49
 */
public boolean carPooling(int[][] trips, int capacity) {
  if (trips == null || trips.length == 0) {
    return true;
  }
  int length = Integer.MIN_VALUE;
  for (int[] trip : trips) {
    if (trip[0] > capacity) {
      return false;
    }
    length = Math.max(length, trip[2]);
  }
  // 差分数组
  int[] diff = new int[length + 1];
  for (int[] trip : trips) {
    int cap = trip[0];
    int start = trip[1];
    int end = trip[2];
    diff[start] += cap;
    if (diff[start] > capacity) {
      return false;
    }
    diff[end] -= cap;
  }
  for (int i = 1; i < diff.length; i++) {
    diff[i] += diff[i - 1];
    if (diff[i] > capacity) {
      return false;
    }
  }
  return true;
}