问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

差分数组详解:从概念到算法实践

创作时间:
作者:
@小白创作中心

差分数组详解:从概念到算法实践

引用
CSDN
1.
https://blog.csdn.net/Captain823Jack/article/details/141109712

差分数组是一种高效处理数组区间修改和查询的算法技巧。与前缀和数组类似,差分数组可以在O(1)时间内完成对数组某个区间的增减操作。本文将详细介绍差分数组的基本概念、实现方法,并通过三个力扣题目展示其在实际算法题中的应用。

差分数组思想

差分数组的核心思想是通过记录相邻元素的差值,来实现对数组区间快速修改。假设我们有一个数组nums,我们需要频繁地对这个数组的某些区间进行增减操作。例如,要求给区间nums[2..6]全部加1,再给nums[3..9]全部减3,再给nums[0..4]全部加2,等等。

常规的做法是使用for循环逐个修改区间内的元素,但这种方法的时间复杂度是O(N),当修改操作频繁时效率会很低。差分数组提供了一种更高效的方法。

我们先对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];
}

通过这个diff差分数组,我们可以反推出原始数组nums

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

关键在于,当我们想对区间nums[i..j]的元素全部加3时,只需要让diff[i] += 3,然后再让diff[j+1] -= 3即可。这样做的原理是:diff[i] += 3意味着给nums[i..]所有的元素都加了3,然后diff[j+1] -= 3又意味着对于nums[j+1..]所有元素再减3,综合起来就是对nums[i..j]中的所有元素都加3。

通过这种方式,我们只需要花费O(1)的时间修改diff数组,就可以完成对nums的区间修改。多次修改diff后,通过diff数组反推,即可得到nums修改后的结果。

经典差分类

为了方便使用,我们可以将差分数组的操作封装成一个类:

// 差分数组工具类
class Difference {
    // 差分数组
    private int[] diff;
    /* 输入一个初始数组,区间操作将在这个数组上进行 */
    public Difference(int[] nums) {
        assert nums.length > 0;
        diff = new int[nums.length];
        // 根据初始数组构造差分数组
        diff[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
    }
    /* 给闭区间 [i,j] 增加 val(可以是负数)*/
    public void increment(int i, int j, int val) {
        diff[i] += val;
        if (j + 1 < diff.length) {
            diff[j + 1] -= val;
        }
    }
    /* 返回结果数组 */
    public int[] result() {
        int[] res = new int[diff.length];
        // 根据差分数组构造结果数组
        res[0] = diff[0];
        for (int i = 1; i < diff.length; i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
}

注意increment方法中的if语句:

public void increment(int i, int j, int val) {
    diff[i] += val;
    if (j + 1 < diff.length) {
        diff[j + 1] -= val;
    }
}

j+1 >= diff.length时,说明是对nums[i]及以后的整个数组都进行修改,那么就不需要再给diff数组减val了。

算法实践

接下来,我们通过三个力扣题目来实践差分数组的应用。

力扣 370 题「区间加法」

直接复用刚才实现的Difference类:

int[] getModifiedArray(int length, int[][] updates) {
    // nums 初始化为全 0
    int[] nums = new int[length];
    // 构造差分解法
    Difference df = new Difference(nums);
    for (int[] update : updates) {
        int i = update[0];
        int j = update[1];
        int val = update[2];
        df.increment(i, j, val);
    }
    return df.result();
}

力扣 1109 航班预定统计

这个题目需要对题目进行联想和抽象:

函数签名如下:

int[] corpFlightBookings(int[][] bookings, int n)

题目实质是:给你输入一个长度为n的数组nums,其中所有元素都是 0。再给你输入一个bookings,里面是若干三元组(i,j,k),每个三元组的含义就是要求你给nums数组的闭区间[i-1,j-1]中所有元素都加上k。请你返回最后的nums数组是多少?

我们可以直接复用差分数组类:

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

力扣 1094 拼车

函数签名如下:

boolean carPooling(int[][] trips, int capacity);

例如:

trips = [[2,1,5],[3,3,7]], capacity = 4

这就不能一次运完,因为trips[1]最多只能上 2 人,否则车就会超载。差分数组的思路是:trips[i]代表着一组区间操作,旅客的上车和下车就相当于数组的区间加减;只要结果数组中的元素都小于capacity,就说明可以不超载运输所有旅客。

车站个数最多为 1000,因此差分数组长度设置为 1001:

boolean carPooling(int[][] trips, int capacity) {
    // 最多有 1000 个车站
    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;
}
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号