差分数组技巧

248 阅读5分钟

数组奇技淫巧之-差分数组

本文主要讲述数组中一种巧妙的解题方法:差分数组,差分数组主要适合的场景为频繁对原始数组中某个区间的元素进行增减

假设有一个数组nums,需要对区间[i, j]之间的元素全部加 1 , 再给区间[k, l]之间的元素全部减 3, 而后再给区间[x, y]之间的元素全部加 2 ,最后再给...

如果对每一个操作都使用先遍历找到指定的区间元素,再进行修改,光是找到找到对应的区间需要O(N)O(N)的时间复杂度了,对于频繁改动的情况,这种效率非常低。

这里可以考虑使用差分数组的技巧。类似于前缀和技巧构造的preSum数组,考虑对nums数组构造一个diff差分数组,diff[i]就是nums[i]nums[i-1]之间的差值。即

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

通过构造出来的差分数组,可以实现对区间O(1)复杂度的实现,只需要找到区间的第一个元素nums[i] = nums[i] + n,区间的最后一个元素的后一个元素nums[j + 1] -= 3

只需要花费o(1)的时间修改diff数组,可以达到对nums[i, j]区间修改的同样效果。

T370-区间加法

见LeetCode第370题[区间加法]

题目描述

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

其中,每个操作会被表示为一个三元组:[startIndex, endIndex, inc],你需要将子数组 A[startIndex ... endIndex](包括 startIndex 和 endIndex)增加 inc

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

我的思路

  • 使用差分数组,记录原始数组的差分数组
  • 根据修改操作,对差分数组区间边界值修改
  • 根据差分数组,还原出原始数组

实现代码

/**
 * 区间加法
 * @param length
 * @param updates
 * @return
 */
public int[] getModifiedArray(int length, int[][] updates) {
    int[] nums = new int[length];
    int[] diff = new int[length];

    for (int[] update : updates) {
        int i = update[0];
        int j = update[1];

        diff[i] += update[2];
        if (j + 1 < length) {
            diff[j + 1] -= update[2];
        }
    }

    // 从 diff 中还原出来原始数组
    for (int i = 1; i < length; i++) {
        nums[i] = nums[i - 1] + diff[i];
    }

    return nums;
}
  • 时间复杂度:每一次对区间操作都是O(1),假设共有m次操作,因此需要O(m)的计算复杂度完成对区间的操作,从diff数组还原到nums数组需要对diff数组进行遍历,还原时间复杂度为O(n),因此共计需要O(m + n)的时间复杂度
  • 空间复杂度:需要额外o(n)的空间存储差分数组

T1109-航班预定统计

见Leetcode第1109题[航班预定统计]

题目描述

这里有 n 个航班,它们分别从 1n 进行编号。

有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firstilasti包含 firstilasti )的 每个航班 上预订了 seatsi 个座位。

请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。

示例 1:

输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5
输出:[10,55,45,25,25]
解释:
航班编号        1   2   3   4   5
预订记录 110  10
预订记录 220  20
预订记录 325  25  25  25
总座位数:      10  55  45  25  25
因此,answer = [10,55,45,25,25]

我的思路

  • 尽管题目描述的十分复杂,但是根据示例中的解释,可以看出本题应该采用差分数组的解法

实现代码

略,和[T370-区间加法]实现代码相同。

T1094-拼车

见LeetCode第1094题[拼车].

题目描述

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

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

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

示例 1:

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

我的思路

  • 通过差分数组求出来行程过程中旅客的动态变化
  • 还原passengers数组,
    • 如果发现某个阶段数量大于capacity,那么直接返回false
    • 否则返回true
  • 根据题意,站点区间为[0, 1000],因此需要创建一个长度为1001的差分数组
  • 需要注意的是,如果在第to个站点下,那么差分数组diff[to]的位置就应该减去乘客数量,而不是diff[to + 1]的位置
/**
 * 拼车:差分数组
 * @param trips
 * @param capacity
 * @return
 */
public boolean carPooling(int[][] trips, int capacity) {
    int stations = 1001; // 最多有 1001 个站点
    int[] passengers = new int[stations]; // 每个站点的人数
    int[] diff = new int[stations]; // 统计人数的变化
    for (int[] trip : trips) {
        int numPassengers = trip[0];
        int from = trip[1];
        int to = trip[2];

        diff[from] += numPassengers;
        if (to< stations) {
            diff[to] -= numPassengers;
        }
    }

    // 还原乘客数量
    passengers[0] = diff[0];
    if (passengers[0] > capacity) return false;
    for (int i = 1; i < stations; i++) {
        passengers[i] = passengers[i - 1] + diff[i];
        if (passengers[i] > capacity) return false;
    }
    return true;
}