前缀和与差分算法总结(C语言)

欢迎来到我的博客

一维前缀和

基本原理

前缀和算法是一种通过预处理数组元素,计算从数组起始位置到当前位置的累积和的方法。这样,我们可以在O(1)时间内获取任意区间的和。

给出一个数组int nums[5] = {1, 2, 3, 4, 5}

它的前缀和为int presum[5] = {1, 3, 6, 10, 15}

{ 1, 1 + 2, 1 + 2 + 3, 1 + 2 + 3 + 4, 1 + 2 + 3 + 4 + 5}

int main() {
	int nums[5] = { 1, 2, 3, 4, 5 };
	int presum[5] = { 1, 3, 6, 10, 15 };
	presum[0] = nums[0];
	for (int i = 1; i < 5; i++) {
		presum[i] = nums[i] + presum[i - 1];
	}
}

用途

1.求数组前n个数之和

求数组前三个数之和:presum[2]

求数组所有数之和:presum[n - 1]

2.求数组区间和

[L,R]---->presum[R] - presum[L - 1]

presum[L - 1]nums 数组中的nums[L - 1]前面所有数字之和(包括它自己)

preSum[R]表示的是 nums数组中num[R]前面所有数字之和(包括它自己)

例如我们想求解nums数组第24个数的区间和,也就是nums数组对应nums[1]nums[3]的区间,我们只需使用presum[3] - presum[1 - 1]即可得到

    int nums[5] = { 1, 2, 3, 4, 5 };
	int presum[5] = { 1, 3, 6, 10, 15 };

presum[3] - presum[0] = (1 + 2 + 3 + 4)-(1)

相减得到2 + 3+ 4

例题
643. 子数组最大平均数 I

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k

请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

任何误差小于 10-5 的答案都将被视为正确答案。

double findMaxAverage(int* nums, int numsSize, int k) {
    // 计算前缀和
    int* presum = (int*)malloc(numsSize * sizeof(int));
    presum[0] = nums[0];
    for (int i = 1; i < numsSize; ++i) {
        presum[i] = presum[i - 1] + nums[i];
    }
    // 初始化最大值为前 k 个元素的和
    int max = presum[k-1];
    // 使用前缀和计算子数组的和
    for (int i = 0; i < numsSize - k; ++i) {
        max = fmax(presum[i + k] - presum[i], max);
    }
    free(presum);
    return (double)max / k;
}

二维前缀和

基本原理

二维前缀和实际上是一个新的二维数组,其中每个元素表示原始矩阵中左上角元素到当前元素的所有元素的和。

1  2   3         1  3  6
4  5   6         5  12  21
7  8   9        12   27  45

二维前缀和数组公式:

prefixSum[i][j] = matrix[i][j] + prefixSum[i−1][j] + prefixSum[i][j−1] - prefixSum[i−1][j−1]

prefixSum[i−1][j]:上方元素 (i-1, j)prefixSum 中的值。
prefixSum[i][j−1]:左方元素 (i, j-1) prefixSum 中的值.
prefixSum[i−1][j−1]:左上方元素 (i-1, j-1)prefixSum 中的值。

在这里插入图片描述

边界问题,需要特判
(i=0,j=0) prefixSum[0][0] = matrix[0][0]
i = 0 prefixSum[0][j] = prefixSum[0][j-1] + matrix[0][j]
j = 0 prefixSum[i][0] = prefixSum[i-1][0] + matrix[i][0]

用途

求二维数组区间和

给出公式
sum[x1,y1][x2,y2] = sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1]

在这里插入图片描述

(i=0,j=0) Sum[0,0][0,0] = matrix[0,0]
x = 0 Sum[0,y1][0,y2] = Sum[0][y2] - Sum[0][y1-1]
y=0 Sum[x1,0][x2,0] = Sum[x1][0] - Sum[x2-1][0]

例题

计算【1,1】【2,3】之间数字和

1  2  3  4
5  6  7  8
9  10 11 12

计算出前缀和数组

1  3  6  10
6  14 24 36
15 33 54 78

sum[2][3] - sum[2][0] - sum[0][3] + sum[0,0]
78-15-10+1 = 54
给出以下代码供参考

#include <stdio.h>
int calculateRegionSum(int prefixSum[4][5], int row_start, int row_end, int col_start, int col_end) {
    return prefixSum[row_end + 1][col_end + 1] - prefixSum[row_start][col_end + 1]
        - prefixSum[row_end + 1][col_start] + prefixSum[row_start][col_start];
}
int main() {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    // 创建一个数组存储前缀和
    int prefixSum[4][5] = { 0 };
    // 计算前缀和数组
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 4; j++) {
            prefixSum[i][j] = matrix[i - 1][j - 1] + prefixSum[i - 1][j] 
                              + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1];
        }
    }
    int sum = calculateRegionSum(prefixSum, 1, 2, 1, 3);
    printf("%d\n", sum);
    return 0;
}

一维差分

差分算法的核心思想是将原始数组转化为差分数组,
其中差分数组的每个元素表示原始数组中相邻元素之差
这种转换可以使得在原数组的某个区间内进行批量更新变得非常高效。

基本原理

差分数组 diff 通过以下方式计算:

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

diff[i] 就表示原数组 nums[i]nums[i-1] 之差
例如
nums[5] = { 1, 5, 8, 9, 6 };
diff[5] = { 1, 4, 3, 1, -3 };
pre[5] = {1, 5 ,8 ,9 ,6}
差分数组的前缀和就是原数组

用途

1.区间增量

给定数组 nums,需要在某个区间 [start, end] 内的所有元素增加一个常量值 val

diff[start] += val; 
diff[end + 1] -= val;

差分数组中还原成原数组的时候start后面的数都会累加,然后再end+1位在减去这个数停止累加

diff[start] += val; 使得presum[start]后面都加上了val,
diff[end + 1] -= val;同理使presum[end + 1]后面都减去val
相当于
presum[start]~presum[end] +val
presum[end+1]~presum[lenth-1]+val-val
相当于
在区间 [start, end] 内的所有元素增加一个常量值 val

接下来我们只需遍历一次差分数组根据前缀和计算就得到更新后的数组。

例如
nums[5] = { 1, 5, 8, 9, 6 }
diff[5] = { 1, 4, 3, 1, -3 }
对以下两个区间内数加val
[1, 3] + 2 ------- diff[1]+=2 diff[4]-=2 ------- diff[5] = { 1, 6, 3, 1, -5 }
[2, 4] + 3 ------- diff[2]+=3 diff[5]-=3 ------- diff[5] = {1, 6, 6, 1, -5 }
diff[5] = {1, 6, 6, 1, -1 }
pre[5] = {1, 7, 13, 14, 9}

这里给出上述例子代码供参考

#include <stdio.h>
void DiffArray(int diff[], int start, int end, int val) {
    diff[start] += val;
    if (end + 1 < 5) {
        diff[end + 1] -= val;
    }
}
int main() {
    int nums[] = { 1, 5, 8, 9, 6 };
    int diff[] = { 1, 4, 3, 1, -3 };
    int presum[5];
    DiffArray(diff, 1, 3, 2);// 区间 [1, 3] 加 2
    DiffArray(diff, 2, 4, 3);// 区间 [2, 4] 加 3
    presum[0] = diff[0];
    for (int i = 1; i < 5; ++i) {
        presum[i] = presum[i - 1] + diff[i];
    }
    for (int i = 0; i < 5; ++i) {
        printf("%d ", presum[i]);
    }
    return 0;
}

二维差分

基本原理

d[x1][y1] += val
d[x2+1][y1] -= val
d[x1][y2+1] -= val
d[x2+1][y2+1] += val
在这里插入图片描述

用途

对以下数组做如下处理
[0,0][2,1]+3
[1,1][2,2]-1

1  2  3  4
5  6  7  8
9  10 11 12

先差分标记[0,0][2,1]+3

3  0 -3  0  0
0  0  0  0  0
0  0  0  0  0
-3 0  3  0  0

再标记 [1,1][2,2]-1

3   0  -3   0  0
0  -1   0   1  0
0   0   0   0  0
-3  1   3  -1  0

计算前缀和

3  3  0  0  0
3  2 -1  0  0
3  2 -1  0  0
0  0  0  0  0

将该数组对应加到原数组

4  5  3  4
8  8  6  8
12 12 10 12

得到答案
给出如下代码供参考:

#include <stdio.h>

void add(int d[4][5],int matrix[3][4], int x1, int y1, int x2, int y2, int val) {
    d[x1][y1] += val;
    d[x2 + 1][y1] -= val;
    d[x1][y2 + 1] -= val;
    d[x2 + 1][y2 + 1] += val;
}
int main() {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int d[4][5] = { 0 };
    add(d,matrix, 0, 0, 2, 1, 3);
    add(d,matrix, 1, 1, 2, 2, -1);
    int prefixSum[4][5] = { 0 };
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 5; j++) {
            if (i == 0 && j == 0) {
                prefixSum[0][0] = d[0][0];
            } else if (i == 0 && j!=0){
                prefixSum[0][j] = prefixSum[0][j - 1] + d[0][j];
            } else if (i != 0 && j == 0) {
                prefixSum[i][0] = prefixSum[i - 1][0] + d[i][0];
            }
            else {
                prefixSum[i][j] = d[i][j] + prefixSum[i - 1][j]
                    + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1];
            }
        }
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            matrix[i][j] += prefixSum[i][j];
        }
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
    return 0;
}


感谢您的阅读

  • 24
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
前缀和差分是一类常用的算法,它们常常被用来优化一些区间操作的问题,如求区间和、区间最大值/最小值等等。下面我们将分别介绍前缀和差分的定义、用法和常见问题。 ## 前缀和 前缀和,顾名思义,就是把前面所有数的和都求出来,用一个数组存起来,以便之后的查询。 ### 定义 给定一个长度为 $n$ 的序列 $a$,令 $s_i = \sum_{j=1}^{i}a_j$,则 $s$ 称为序列 $a$ 的前缀和数组。 ### 用法 前缀和的主要作用是用 $O(1)$ 的时间复杂度求出一个区间 $[l,r]$ 的和,即 $s_r - s_{l-1}$。这是因为 $s_r$ 存储了序列从 $1$ 到 $r$ 的和,而 $s_{l-1}$ 存储了序列从 $1$ 到 $l-1$ 的和,因此区间 $[l,r]$ 的和可以通过两个前缀和相减计算得出。 前缀和的时间复杂度为 $O(n)$,因为需要遍历一遍序列求出前缀和数组。但是,如果有多个查询需要求区间和,那么使用前缀和可以将每次查询的时间复杂度降低到 $O(1)$。 ### 代码实现 下面是使用前缀和求区间和的代码实现: ```cpp vector<int> a; // 原序列 vector<int> s(a.size() + 1); // 前缀和数组 // 计算前缀和 for (int i = 1; i <= a.size(); i++) { s[i] = s[i - 1] + a[i - 1]; } // 查询区间 [l, r] 的和 int sum = s[r] - s[l - 1]; ``` ## 差分 差分前缀和相反,它主要用来对区间进行修改。我们可以利用差分数组进行区间修改,并最终得到修改后的序列。 ### 定义 给定一个长度为 $n$ 的序列 $a$,令 $d_i = a_i - a_{i-1}$($d_1 = a_1$),则 $d$ 称为序列 $a$ 的差分数组。 ### 用法 差分的主要作用是对区间进行修改。假设我们需要将区间 $[l,r]$ 的数加上 $k$,我们可以将差分数组的 $d_l$ 加上 $k$,将 $d_{r+1}$ 减去 $k$。这样,对差分数组求前缀和,就可以得到修改后的序列。 具体来说,我们可以按照以下步骤进行区间修改: 1. 对差分数组的 $d_l$ 加上 $k$; 2. 对差分数组的 $d_{r+1}$ 减去 $k$; 3. 对差分数组求前缀和,得到修改后的序列。 差分的时间复杂度为 $O(n)$,因为需要遍历一遍序列求出差分数组。但是,如果有多次区间修改需要进行,那么使用差分可以将每次修改的时间复杂度降低到 $O(1)$。 ### 代码实现 下面是使用差分进行区间修改的代码实现: ```cpp vector<int> a; // 原序列 vector<int> d(a.size() + 1); // 差分数组 // 计算差分数组 for (int i = 1; i < a.size(); i++) { d[i] = a[i] - a[i - 1]; } // 修改区间 [l, r],将数加上 k d[l] += k; d[r + 1] -= k; // 对差分数组求前缀和,得到修改后的序列 for (int i = 1; i < d.size(); i++) { a[i] = a[i - 1] + d[i]; } ``` ## 常见问题 ### 1. 差分数组的长度是多少? 差分数组的长度应该比原序列长度多 1,因为 $d_1 = a_1$。 ### 2. 什么情况下使用前缀和?什么情况下使用差分? 如果需要进行多次区间查询,那么使用前缀和可以将每次查询的时间复杂度降低到 $O(1)$;如果需要进行多次区间修改,那么使用差分可以将每次修改的时间复杂度降低到 $O(1)$。 ### 3. 前缀和差分的本质区别是什么? 前缀和差分都是用来优化区间操作的算法,它们的本质区别在于: - 前缀和是通过预处理前缀和数组来优化区间查询; - 差分是通过预处理差分数组来优化区间修改。 ### 4. 前缀和差分能否同时使用? 当然可以。如果需要同时进行区间查询和修改,我们可以先使用差分数组对区间进行修改,然后再对差分数组求前缀和,得到修改后的序列。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值