前言: 我这个笨笨的脑子,是真的不适合学算法,但是为了第 101 次的成功,我要尝试第 100 次
一、差分用来解决什么问题
差分常常用来解决区间操作问题,一维数组或者二维数组的区间操作,能可以用差分解决,比如对一维数据的某个区间加上 d ,操作 m 次 。 或者对二维数组的某个区间加上 d ,操作 m 次。
差分法用来解决的问题示例
- 给定一个整数数组 nums,处理以下类型的多个查询: 计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
- 给定一个二维矩阵 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;
}