差分与前缀和 --- 详解

引入

  • 假如给定一个数组 a, 需要在数组 a 中进行如下操作:
    • 对数组下标范围为 【L,R】中的所有数都加上一个常数 K
  • 常见的解决办法:
    • 使用 for 循环遍历数组,逐个加上 K
    • 但可惜的是,这样做的时间复杂度为 O(n)
  • 快速且巧妙的实现办法:使用差分算法,它的时间复杂度只有 O(1)

简介

什么是差分?

  • 差分是前缀和的逆运算,即:
    • 给定数组a:a[1],a[2],a[3], …… a[n]
    • 构造数组b:b[1],b[2],b[3],…… b[n]
    • 使得 a[i] = b[1] + b[2] + …… + b[i]; (即数组 a 是数组 b 的前缀和)
    • b 称为 a 的差分,a 称为 b 的前缀和。

计算构造数组

  • 参考条件:已知数组 a , 并且 a[i] = b[1]+b[2]+……+b[i];
  • 因此:
    • b[1] = a[1]
    • b[2] = a[2] - a[1]
    • b[3] = a[3] - a[2]
    • b[i] = a[i] + a[i - 1] (因为 a[i] 是 数组 b 的 前 i 项和,而 a[i- 1] 是 数组 b 的 前 i - 1 项和。两者相减,便得出 b[i])
  • 结论:
    • 计算构造数组 b[i] 的值有如下计算公式:
      • 当 i = 1 时,b[1] = a[1]
      • 当 i > 1时, b[i] = a[i] + a[i - 1]

差分的作用和原理

  • 作用:
    • 如引入所述,使用差分可以实现用 O(1) 的时间复杂度给原数组 a 中间的某一段连续区间全部加上一个常数 K
  • 原理:
    • 目标:a[L] + K , a[L+1] + K ,a[L+2] + K ,… ,a[R] + K
    • 根据 a[i] = b[1] + b[2] + …… + b[i] 可知:
      • 若 b[i] + K,则 a[i] ~ a[a.len - 1] 中所有数都加 K (因为数组 a 是 数组 b 的前缀和),
      • 因为我们需要 a[L] ~ a[R] 中所有数都加 K,
      • 所以:
        • b[L] + k :使得 a[L] ~ a[a.len - 1] 中的所有数全部加上 k
        • b[R + 1] - k : 使得 a[R + 1] ~ a[a.len - 1] 中的所有数全部减去 k
    • 结论:
      • 若需将 a[L] ~ a[R] 中的所有数都加 1,可进行如下步骤实现:
        • 构造数组 b[L] + K
        • 构造数组 b[R + 1] - K (注意在计算过程中确保不超出数组范围)

还原原数组

  • 参考条件:a[i] = b[1] + b[2] + …… + b[i];
  • 因此:
    • a[1] = b[1]
    • a[2] = b[2] + a[1]
    • a[3] = b[3] + a[2]
    • a[i] = b[i] + a[i-1]
  • 结论:
    • 还原原数组 b[i] 的值有如下计算公式:
      • 当 i = 1 时,a[1] = b[1]
      • 当 i > 1时, a[i] = b[i] + a[i - 1]

实践

  • 通过以上的分析,现在就可以来解决引入时遇到的问题:
	public static void main(String[] args) {  
	    Scanner sc = new Scanner(System.in);  
	    int n = sc.nextInt();  
	    int[] a = new int[n];  
	    int l = sc.nextInt(), r = sc.nextInt(), k = sc.nextInt();  
	    for (int i = 0; i < n; i++) {  
	        a[i] = sc.nextInt();  
	    }  
	    // 计算构造数组  
	    int[] b = new int[n];  
	    b[0] = a[0];  
	    for (int i = 1; i < n; i++) {  
	        b[i] = a[i] - a[i-1];  
	    }  
	    // 差分  
	    b[l] += k; b[r + 1] -= k;  
	    // 还原原数组  
	    a[0] = b[0];  
	    for (int i = 1; i < n; i++) {  
	        a[i] = b[i] + a[i - 1];  
	    }  
	    // 输出  
	    for (int i = 0; i < n; i++) {  
	        System.out.print(a[i] + " ");  
	    }  
	}
  • 测试用例:
	5 1 3 1
	1 2 3 4 5
	1 3 4 5 5 

总结

  • 已知原数组 a ,数组 b 是 a 的差分数组,则:
    • 计算差分数组 b:
      • 当 i = 1 时,b[1] = a[1]
      • 当 i > 1时, b[i] = a[i] + a[i - 1]
    • 通过差分数值,计算数值 a 某一区间加上常数 k 后的数组
      • b[L] + K
      • b[R + 1] - K (注意在计算过程中确保不超出数组范围)
    • 还原原数组 b:
      • 当 i = 1 时,a[1] = b[1]
      • 当 i > 1时, a[i] = b[i] + a[i - 1]
  • 注意:
    • 前缀和与差分都是离线的
      • 即:原数组 a 与 差分数组 b 在修改数据时,不同步,需要不断更新实现同步。
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值