【算法题】关于差分,我只需要知道....

前言: 我这个笨笨的脑子,是真的不适合学算法,但是为了第 101 次的成功,我要尝试第 100 次

一、差分用来解决什么问题

差分常常用来解决区间操作问题,一维数组或者二维数组的区间操作,能可以用差分解决,比如对一维数据的某个区间加上 d ,操作 m 次 。 或者对二维数组的某个区间加上 d ,操作 m 次。

差分法用来解决的问题示例

  1. 给定一个整数数组 nums,处理以下类型的多个查询: 计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
  2. 给定一个二维矩阵 matrix,具有以下类型的多个请求:计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。

二、差分法的思想核心

通过引入差分数组 D[n],找出(×,不用咱找,就两个关系,咱记住就行了 )差分数组和原 nums 数组间的关系,修改时只操作差分数组,最后再用差分数组得到修改后的原数组,从而实现使用 O(m)时间复杂度解决问题 – m 为请求操作次数

1. 一位数组的差分

我们需要记住以下两种关系:

D[i] = nums[i] - nums[i-1]
nums[i] = D[0] + D[1] + D[2] + D[3] + D[4] + ... + D[i] = nums[i-1] + D[i]

D[i] 和 nums[i] 的关系,可以用如下图数轴表示:

在这里插入图片描述

当我们对 [L , R] 区间执行加 d 操作时,可以转化为以下两种操作:

D[left] += d;
D[R+1] -= d;

为什么呢? 可以通过分析以下得到:

nums[Left] = D[0] + D[1] + D[2] + D[3] + D[4] + ... + D[Left];
nums[right] =  D[0] + D[1] + D[2] + D[3] + D[4] + ... + D[Left] + .... + D[Right];
nums[k (k > right)] =  D[0] + D[1] + D[2] + D[3] + D[4] + ... + D[Left] + .... + D[Right] + D[Right + 1] + ... + D[n - 1];

所以我们对 D[left] + d 可以使得原数组区间 [0, Left - 1] 不变, [Left , Right] 区间的元素都被加上 d ,同时因为对 D[R] -= d,可以使得 [Right+1, n-1] 的元素不变 ( + n 和 - n 抵消了)

一维差分完整代码

public class Diff {
    public static void main(String[] args) {
        NumArray numArray = new NumArray(new int[]{1,1,1,0,1});
        numArray.range(0, 1);
        numArray.range(1,2);
        int[] nums = numArray.getNumsAns();
        for (int i=0; i<nums.length; i++){
            System.out.print(nums[i]);
        }
    }

}

// 1. 一维数组的差分
class NumArray {
    int[] D;
    public NumArray(int[] nums) {
        this.D = new int[nums.length];
        D[0] = nums[0];
        for (int i=1; i<nums.length; i++){
            D[i] = nums[i] - nums[i-1];
        }
    }
    // 对 [left, right] 区间元素 + 1
    public void range(int left, int right) {
        D[left] += 1;
        if(right != D.length - 1) {
            D[right + 1] -= 1;
        }
    }

    public int[] getNumsAns() {
        // 最终结果的 nums 为
        int[] numsAns = new int[D.length];
        numsAns[0] = D[0];
        for (int i=1; i<D.length; i++){
            numsAns[i] = numsAns[i - 1] + D[i];
        }
        return numsAns;
    }
}

2. 二维数组的差分

我们需要记住下面两种关系:

D[i][j] = nums[i][j] - nums[i-1][j] - nums[i][j-1] + nums[i-1][j-1]; 
nums[i][j] = D[i][j] + nums[i−1][j]+nums[i][j−1]−nums[i−1][j−1]

当我们对一个子矩阵 { (L, D), (R, U) } 执行加 d 操作时,需要对差分数组进行的操作为:

D[L][D] += d
D[L][U + 1] -= d // L 看成常数
D[R + 1][D] -= d // D 看成常数
D[R+1][U+1] += d 因为前面多减了一个 d

在这里插入图片描述

二维差分代码

    public int[][] rangeAddQueries(int n, int[][] queries) {
        // 二维差分模板
        int[][] diff = new int[n + 2][n + 2], ans = new int[n][n];
        for (int[] q : queries) {
            int L = q[0], D = q[1], R = q[2], U = q[3];
            ++diff[L + 1][D + 1];
            --diff[L + 1][U + 2];
            --diff[R + 2][D + 1];
            ++diff[R + 2][U + 2];
        }
        // 用二维前缀和复原
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                ans[i - 1][j - 1] = diff[i][j] += diff[i][j - 1] + diff[i - 1][j] - diff[i - 1][j - 1];
        return ans;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值