今日题目

简介

前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。有关它的介绍请参考:2022.08.09 小而美的算法技巧:前缀和数组

而差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。

类似前缀和技巧构造的 preSum 数组,使用差分技巧是对原数组 nums 构造一个 diff 差分数组,diff[i] ****就是 nums[i] 和 nums[i-1] 之差。

通过这个 diff 差分数组可以反推出原始数组 nums 的,代码逻辑如下:

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

利用构造的差分数组 diff,可以快速进行区间增减的操作。

例如想对从 i 到 j 之间的元素全部加 3,那么只需要让 diff[i] += 3,然后再让 diff[j+1] -= 3 即可。

这是因为从差分数组推导回原数组时,后一项需要依赖前一项的推导结果。

例如执行 diff[i] += 3,意味着推导出的 res[i] 会比原结果多 3。而推导 res[i+1] 又需要用到刚推导出的 res[i],意味着 res[i] 比原结果多 3 这个误差会延续到 res[i+1] 的推导中。

只要花费 O(1) 的时间修改 diff 数组,就相当于给 nums 的整个区间做了修改。多次修改 diff,然后通过 diff 数组反推,即可得到 nums 修改后的结果。

可以把差分数组抽象成一个类,包含 increment 方法和 result 方法,以便在后续使用:

class Difference {
    constructor(nums) {
        this.diff = new Array(nums.length).fill(0);
        this.diff[0] = nums[0];
        for (let i = 1; i < nums.length; i++) {
            this.diff[i] = nums[i] - nums[i - 1];
        } 
    }
    
    increment(i, j, val) {
        this.diff[i] += val;
        // 当 j+1 >= diff.length 时,
        // 说明是对 nums[i] 及以后的整个数组都进行修改,
        // 那么就不需要再给 diff 数组减 val 了。
        if (j + 1 < this.diff.length) {
            this.diff[j + 1] -= val;
        }
    }
    
    result() {
        const res = new Array(this.diff.length);
        // 根据差分数组构造结果数组
        res[0] = this.diff[0];
        for (let i = 1; i < this.diff.length; i++) {
            res[i] = res[i - 1] + this.diff[i];
        }
        return res;
    }
}

小抄

小而美的算法技巧:差分数组