【算法】-前缀和与差分

目录

前缀和

从「一维数组的动态和」说起

前缀和是什么

前缀和有什么用

用途一:求数组前 i 个数之和

用途二:求数组的区间和

 二维数组的前缀和(子矩阵的和)

差分

一维差分

(1)如何构造差分b数组?

(2)构造差分数组有什么用?

(3)如何求经过操作后的原数组呢

二维差分

(1)如何构造差分数组?

(2)如何给子矩阵中的每一个数都加上c?

(3)如何求经过操作后的原数组?


前缀和

从「一维数组的动态和」说起

        前缀和,英文是 preSum,是一种基础算法。

        为了了解背景,咱们先看 LeetCode 上一个简单题目:1480. 一维数组的动态和

        这个题让我们求 runningSum[i] = sum(nums[0]…nums[i]),如果你没有了解过「前缀和」,可能会写出两重循环:每个 runningSum[i],累加从 0 位置到 i 位置的 nums[i]。即,写出下面的代码:

int* runningSum(int* nums, int numsSize, int* returnSize){
    int* preSum = malloc(sizeof(int) * numsSize);
    *returnSize = numsSize;
    for (int i = 0; i < numsSize; ++i) {
        int sum = 0;
        for (int j = 0; j <= i; ++j) {
            sum += nums[j];
        }
        preSum[i] = sum;
    }
    return preSum;
}

        两重循环的时间复杂度是 O(n*2),效率比较低。 ​

        其实我们只要稍微转变一下思路,就发现没必要用两重循环。 ​当已经求出 runningSum[i] = sum(nums[0]…nums[i]) 那么 runningSum[i + 1] = sum(nums[0]…nums[i]) + nums[i + 1] = runningSum[i] + nums[i + 1]

int* runningSum(int* nums, int numsSize, int* returnSize){
    int* preSum = malloc(sizeof(int) * numsSize);
    *returnSize = numsSize;
    for (int i = 0; i < numsSize; ++i) {
        if (i == 0) {
            preSum[i] = nums[i];
        } else {
            preSum[i] = preSum[i - 1] + nums[i]; 
        }
    }
    return preSum;
}

前缀和是什么

        通过上面的内容,很容易理解preSum 数组其实就是「前缀和」。 ​         

        「前缀和」 是从 nums 数组中的第 0 位置开始累加,到第 i 位置的累加结果,我们常把这个结果保存到数组 preSum 中,记为 preSum[i]。         

        在前面计算「前缀和」的代码中,计算公式为 preSum[i] = preSum[i - 1] + nums[i] ,为了防止当 i = 0 的时候数组越界,所以加了个 if (i == 0) 的判断,即 i == 0 时让 preSum[i] = nums[i]。 ​

        在其他常见的写法中,为了省去这个 if 判断,我们常常把「前缀和」数组 preSum 的长度定义为原数组长度+1。preSum 的第 0 个位置,相当于一个占位符,置为 0。 那么就可以把 preSum 的公式统一为 preSum[i] = preSum[i - 1] + nums[i - 1],此时的 preSum[i] 表示 nums 中 iii 元素左边所有元素之和(不包含当前元素 iii)。 ​         

        下面以 [1, 12, -5, -6, 50, 3] 为例,用动图讲解一下如何求 preSum。

1614561004-TEQwGZ-303,preSum.gif

上图表示:

  • preSum[0] = 0;
  • preSum[1] = preSum[0] + nums[0];
  • preSum[2] = preSum[1] + nums[1];
  • ...

前缀和有什么用

用途一:求数组前 i 个数之和

        求数组前 i 个数之和,是「前缀和」数组的定义,所以是最基本的用法。

        如果要求 nums 数组中的前 2 个数的和(即 sum(nums[0], nums[1])) ,直接返回 preSum[2] 即可。 同理,如果要求 nums 数组中所有元素的和(即 sum(nums[0]..nums[length−1])),直接返回 preSum[length] 即可。

用途二:求数组的区间和

        利用 preSum 数组,可以在 O(1) 的时间内快速求出 nums 任意区间 [i,j](两端都包含) 内的所有元素之和

        公式为: sum(i ,j) = preSum[j+1] − preSum[i];

        其实就是消除公共部分即 0~i-1 部分的和,那么就能得到 i~j 部分的区间和。注意上面的式子中,使用的是 preSum[j + 1] 和 preSum[i]。

  • preSum[j + 1] 表示的是 nums 数组中 [0,j] 的所有数字之和(包含 0 和 j)。
  • preSum[i] 表示的是 nums 数组中 [0,i−1] 的所有数字之和(包含 0 和 i−1)。
  • 当两者相减时,结果留下了 nums 数组中 [i,j] 的所有数字之和。

        接下来我们以 643. 子数组最大平均数 I 为例,这个题目要求数组nums中所有长度为k的连续子数组中的最大的平均数。

        这个题可以用「前缀和」来解决,也可以用固定大小为 k 的「滑动窗口」来解决。

        要求大小为 k 的窗口内的最大平均数,可以求 [i, i + k] 区间的最大「和」再除以 k,即要求 (preSum[i] - preSum[i - k]) / k 的最大值。


        总之,如果题目要求「区间和」的时候,那么就可以考虑使用「前缀和」。

double findMaxAverage(int* nums, int numsSize, int k) {
    double preSum[numsSize + 1];
    preSum[0] = 0;
    double max = INT_MIN; 
    for (int i = 1; i <= numsSize; ++i) { 
        preSum[i] = preSum[i - 1] + nums[i - 1];
        if(i - k >= 0
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值