【数据结构】差分数组

【数据结构】差分数组

    • 差分数组
    • 二维差分数组
      • 二维数组的前缀和

差分数组

如果给定一个包含1000万个元素的数组,同时假定会有频繁区间修改操作,但是不会有频繁的查询操作,比如对某个范围【l,r】内的数字加上某个数字,此时,如果直接在原数组上进行操作,无疑效率会十分低下。因此我们使用差分数组标记区间【l,r】的修改操作,然后在查询时根据差分数组还原即可,差分数组就是用来标记对数组修改记录的。

那么怎么操作呢?

假设我们存在数组arr[],我们使用另外一个数组diff表示arr数组中每个元素与前一个元素的差值,其中diff[i] = arr[i] - arr[i - 1]的值,对于diff[0] = arr[0],形式如下,其中diff就是我们的差分数组。
【数据结构】差分数组_第1张图片

这个差分数组有什么用呢?

我们可以看到当我们要查询arr的时候,需要根据差分数组进行还原,对于arr[i],我们只需要计算arr[i] == arr[i - 1] + diff[i]即可,arr[0] = 0 + diff[0],可以看到在还原的过程中,diff[0,…,i - 1]的值会通过赋值给arr[0,…,i-1]间接影响到arr[i],这样,就只需要修改diff[l]和diff[r + 1]的值就可以达到区间修改的目的,因为修改diff[l],其后的元素都会在累加的过程中受到影响,在diff[r + 1]将修改还原,r之后的元素就不会受到影响。比如我们需要对arr区间【2,4】的元素加上2,可以看到diff中只有2和5的位置发生了改变,这样每次对区间操作,我们都只需要改变差分数组中两个值就可以了,这样可以大大提高效率。
【数据结构】差分数组_第2张图片

了解差分数组的特性之后,我们可以看下leetcode中的一道题:
使数组中的所有元素都等于零
对于该题,我们需要每次选择一个长度为k的子数组,将其减去1,最终判断能否将数组的值全部置为0。由于数据范围很大,两层循环显然不太合适,我们可以使用差分数组。由于是子数组,且必须将所有的元素都置为0,所以我们需要从0的位置的第一个元素开始遍历,不能跳过任意一个元素,我们使用sumD表示前面已经累积需要减去的最大元素,如果当前元素大小不足以减去该元素,或者后面已经没有元素可以配合当前元素使之变为0,则返回false,否则返回true。

class Solution {
    public boolean checkArray(int[] nums, int k) {
        int n = nums.length, sumD = 0;
        var d = new int[n + 1];
        for (int i = 0; i < n; i++) {
            sumD += d[i];
            int x = nums[i];
            x += sumD;
            if (x == 0) continue; // 无需操作
            if (x < 0 || i + k > n) return false; // 无法操作
            sumD -= x; // 直接加到 sumD 中
            d[i + k] += x;
        }
        return true;
    }
}
作者:endlesscheng
链接:https://leetcode.cn/problems/apply-operations-to-make-all-array-elements-equal-to-zero/solution/chai-fen-shu-zu-pythonjavacgojs-by-endle-8qrt/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二维差分数组

如果我们是对二维数组进行频繁修改,我们可以使用二维差分数组记录修改记录,对于arr[][] ,我们使用diff[][]标记二维数组,通过一维数组我们可以观察到一维数组的值是差分数组的前缀和,推广到二维数组,arr[i][j]也表示diff[i][j]的前缀和。

二维数组的前缀和

计算公式如下。
diff[i][j]=arr[i][j]−arr[i−1][j]−arr[i][j−1]+arrr[i−1][j−1]

对坐标点 (x1, y1) ~ (x2, y2)所围成的区间进行修改时,对应的4个端点的操作应为:

diff[x1][y1] += c;
diff[x1][y2 + 1] -= c;
diff[x2 + 1][y1] -= c;
diff[x2 + 1][y2 + 1] += c;

从二维前缀和的角度来看,对区域左上角 +1,会对所有右下位置产生影响,那么在区域右上角的右边相邻处和左下角的下边相邻处 −1,可以消除这个影响,但是两个 −1又会对区域右下角的右下所有位置产生影响,所以要在右下角的右下相邻处再 +1还原回来。

我们看一题
子矩阵元素加 1

class Solution {
    public int[][] rangeAddQueries(int n, int[][] queries) {
        int[][] diff = new int[n + 1][n + 1],  ans = new int[n][n];
        for (int[] q : queries) {
            int r1 = q[0], c1 = q[1], r2 = q[2], c2 = q[3];
            ++diff[r1][c1];
            --diff[r1][c2 + 1];
            --diff[r2 + 1][c1];
            ++diff[r2 + 1][c2 + 1];
        }
        ans[0][0] = diff[0][0];
        for (int i = 1; i < n; ++i) {
            ans[0][i] = diff[0][i] +=  ans[0][i - 1];
            ans[i][0] = diff[i][0] +=  ans[i - 1][0];
        }

        for (int i = 1; i < n; ++i) {
            for (int j = 1; j < n; ++j) {
                ans[i][j] = diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];
            }
        }

        return ans;
    }
}

你可能感兴趣的:(数据结构和算法,数据结构)