C++中前缀和数组(算法)基本介绍

 更新时间:2024年12月13日 11:54:07   作者:猫咪-9527  
前缀和(Prefix Sum)是指数组中某个位置之前的所有元素的和,本文将介绍C++中前缀和数组(算法)基本概念,感兴趣的朋友一起看看吧

1.前言

如何快速求得一个数组的前缀和?

1. 1 前缀和的基本概念

前缀和(Prefix Sum)是指数组中某个位置之前的所有元素的和。对于数组arr,其前缀和数组prefixSum定义为:

  • prefixSum[0] = 0prefixSum[i] = prefixSum[i-1] + arr[i-1](对于i > 0
  • 这样,任意子数组arr[l...r]的和可以通过prefixSum[r+1] - prefixSum[l]快速计算出来。

2.一维数组的前缀和

在处理数组区间和问题时,前缀和(Prefix Sum)是一个非常有效的工具,可以大大加快查询速度。下面详细解释如何预处理前缀和数组,并使用前缀和数组快速计算任意区间的元素和。

步骤一:预处理前缀和数组

给定一个数组arr,长度为n,我们可以预处理一个前缀和数组dp,其中dp[i]表示数组arr从索引1到索引i的所有元素的和。

初始化dp[0] = 0(这是为了方便计算从索引1开始的区间和)。

使用递推公式dp[i] = dp[i - 1] + arr[i - 1]来计算dp数组的每个元素。注意,这里arr的索引从0开始,而dp的索引

1开始模拟区间[1, i]//从1开始是为了避免从0开始时会出现-1,从而进行判断,减少复杂边界情况

  • 12345
012345

!!!此步是为了处理边界情况

具体代码如下:

std::vector<int> dpsum(const vector<int>& arr) {
    int n = arr.size();
    vector<int> dp(n + 1, 0); // dp 数组的长度是 n+1,并初始化为 0
    for (int i = 1; i <= n; ++i) {
        dp[i] = dp[i - 1] + arr[i - 1];
    }
    return dp;
}

步骤二:使用前缀和数组快速计算区间和

给定一个区间[l, r],我们可以利用预处理好的前缀和数组dp快速计算区间内所有元素的和。

区间[l, r]内所有元素的和为dp[r] - dp[l - 1]

具体解释如下:

  • dp[r]包含从索引1到索引r的所有元素的和。
  • dp[l - 1]包含从索引1到索引l - 1的所有元素的和。
  • 因此,dp[r] - dp[l - 1]正好是区间[l, r]内所有元素的和。

具体代码如下:

int queryRangeSum(const vector<int>& dp, int l, int r) {
    return dp[r] - dp[l - 1];
}

下面展示一个一维数组前缀和模板

#include <iostream>
#include <vector>
using namespace std;
// 预处理前缀和数组的函数
vector<int> dpSum(const vector<int>& arr) {
    int n = arr.size();
    std::vector<int> dp(n + 1, 0); // dp 数组的长度是 n+1,并初始化为 0
    for (int i = 1; i <= n; ++i) {
        dp[i] = dp[i - 1] + arr[i - 1];
    }
    return dp;
}
// 查询区间和的函数
int queryRangeSum(const vector<int>& dp, int l, int r) {
    return dp[r] - dp[l - 1];
}
int main() {
    // 示例数组
    vector<int> arr = {1, 2, 3, 4, 5};
    // 预处理前缀和数组
    vector<int> dp = dpSum(arr);
    // 查询区间 [2, 4] 的和(注意C++中数组索引从0开始,但这里的l,r是区间表示,从1开始考虑)
    int sum = queryRangeSum(dp, 2, 4);
    cout << "Sum of range [2, 4]: " << sum << endl; // 输出 9 (2+3+4)
    return 0;
}

3.二维数组求前缀和

二维数组求前缀和和一维数组求前缀和思路相似,都是通过预处理前缀和来解决此类问题。

类⽐于⼀维数组的形式,如果我们能处理出来从 [0, 0] 位置到 [i, j] 位置这⽚区域内所有 元素的累加和,就可以在 O(1) 的时间内,搞定矩阵内任意区域内所有元素的累加和。因此我们 接下来仅需完成两步即可:

123
456
789
0000
0123
0456
0789
for(int i = 1; i <= n; i++) 
 for(int j = 1; j <= m; j++)
 cin >> arr[i][j];

这样,我们填写前缀和矩阵数组的时候,下标直接从 1 开始,能⼤胆使⽤ i - 1 , j - 1 位 置的值。

此时我们所求的[i,j]坐标的和,即为下图蓝色区域位置

96284d6e93814b7594510c6a56b421fe.png

此时我们所求的[i,j]坐标的和,即为下图蓝色区域位置

58ac883bdd4f4c1ea216117335597e87.png

 // 处理前缀和矩阵
 for(int i = 1; i <= n; i++)
 for(int j = 1; j <= m; j++)
 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 
1];

将表格抽象为四部分:分别为绿,粉,蓝,白

x

i

0

0

0

0

j

0

1

2

3

0

4

5

6

y

0

7

8

9

如果我们想求出白色部分的和

即为:总-绿-粉-蓝=白;

绿+粉=dp[i][j];

绿+蓝=dp[x][y];

白=dp[i][y]-dp[x][y]-dp[i][j]+dp[x][j];

总代码如下

#include <iostream>
using namespace std;
const int N = 1010;
int arr[N][N];
long long dp[N][N];
int n, m, q;
int main() 
{
 cin >> n >> m >> q;
 // 读⼊数据
 for(int i = 1; i <= n; i++) 
 for(int j = 1; j <= m; j++)
 cin >> arr[i][j];
 // 处理前缀和矩阵
 for(int i = 1; i <= n; i++)
 for(int j = 1; j <= m; j++)
 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 
1];
 // 使⽤前缀和矩阵
 int x1, y1, x2, y2;
 while(q--)
 {
 cin >> x1 >> y1 >> x2 >> y2;
 cout << dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 
1] << endl;
 }
}

4.二维矩阵中心前缀和

二维矩阵中心前缀和求取方式和二维矩阵前缀和方法类似,不过需要多进行处理几步。

中心坐标[x,y]的宽k=1中心前缀和,为以下蓝色部分

x

y

1

2

3

4

5

6

7

8

9

坐标[x,y]的中心前缀和,为以下蓝色部分

x

1

2

3

y

4

5

6

7

8

9

此时步骤为:

1.先求普通二维前缀和dp,建立首行列都为0的普通前缀和数组

x

i

0

0

0

0

j

0

1

3

6

0

5

12

21

y

0

12

27

55

2.求建立新的中心前缀和数组即为(去掉为0的辅助项):

x1=max(0,i-k);y1=max(0,j-k);//避免出现越界情况

arr[i][j]=dp[x1-1][y1-1]+dp[x2][y2]-dp[x1-1][y2]-dp[x2][y2]

以下是完整代码:

class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        int m=mat.size(),n=mat[0].size();
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mat[i-1][j-1];
            }
        }
         vector<vector<int>> arr(m,vector<int>(n));
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                int x1=max(i-k,0)+1;
                int y1=max(j-k,0)+1;
                int x2=min(i+k,m-1)+1;
                int y2=min(j+k,n-1)+1;
               arr[i][j]=dp[x1-1][y1-1]+dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]; 
            }
        }
        return arr;
    }
};

5.前缀和与哈希表相结合

前缀和与哈希表相结合是一种非常有效的算法技巧,常用于解决数组或字符串中与子数组(或子串)和相关的问题。这种方法的核心思想是通过前缀和来快速计算任意子数组的和,并利用哈希表来记录这些和出现的频率,从而高效地解决问题。

1. 哈希表的作用

哈希表(Hash Table)用于记录前缀和出现的频率。在处理问题时,我们可以利用哈希表快速查找某个前缀和是否已经出现过,以及出现了多少次。

(假设需要求出i前面的等于k的子数组个数)

0x1x2……i

对于i来说数组可以分为两段

ki的前缀和-ki

2. 前缀和与哈希表相结合的应用

假设我们需要求出数组中所有和为k的子数组的个数。我们可以按照以下步骤进行:

  • 初始化一个哈希表count,用于记录前缀和出现的频率。将count[0]初始化为1,表示前缀和为0的情况(即空子数组)出现了1次。
  • 初始化一个变量result0,用于记录和为k的子数组的个数。
  • 遍历数组arr,计算当前位置的前缀和prefixSum[i]
  • 计算目标前缀和target = prefixSum[i] - k。如果target在哈希表中出现过,说明存在一个或多个子数组的和为k(这些子数组的右端点是当前位置i,左端点可以通过哈希表中的记录确定)。
  • result增加count[target]的值。
  • 更新哈希表count,将prefixSum[i]的频率增加1
  • 遍历结束后,result就是和为k的子数组的个数。
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
int numSubarraySumEqualK(vector<int>& nums, int k) {
    unordered_map<int, int> count; // 哈希表,记录前缀和出现的频率
    count[0] = 1; // 初始化前缀和为0的频率为1(表示空子数组)
    int prefixSum = 0; // 当前位置的前缀和
    int result = 0; // 和为k的子数组个数
    for (int num : nums) {
        prefixSum += num; // 计算当前位置的前缀和
        int target = prefixSum - k; // 计算目标前缀和
        if (count.find(target) != count.end()) {
            // 如果目标前缀和在哈希表中出现过,则增加结果
            result += count[target];
        }
        // 更新哈希表中当前前缀和的频率
        count[prefixSum]++;
    }
    return result;
}
int main() {
    vector<int> nums = {1, 1, 1}; // 示例数组
    int k = 2; // 目标和
    int result = numSubarraySumEqualK(nums, k);
    cout << "和为" << k << "的子数组个数为: " << result << endl;
    return 0;
}

到此这篇关于C++中前缀和数组(算法)基本介绍的文章就介绍到这了,更多相关c++前缀和数组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Qt中QUndoView控件的具体使用

    Qt中QUndoView控件的具体使用

    QUndoView是Qt框架中用于可视化显示QUndoStack内容的控件,本文主要介绍了Qt中QUndoView控件的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2025-04-04
  • 如何利用Matlab绘制出好看的火山图

    如何利用Matlab绘制出好看的火山图

    火山图是散点图的一种,它将统计测试中的统计显著性量度和变化幅度相结合,从而能够帮助快速直观地识别那些变化幅度较大且具有统计学意义的数据点。本文将通过Matlab绘制好看的火山图,需要的可以参考一下
    2022-03-03
  • C++ STL容器详解之红黑树部分模拟实现

    C++ STL容器详解之红黑树部分模拟实现

    本文主要对红黑树进行了详细介绍,并对其核心功能进行了模拟实现。文中的代码对我们的学习或工作有一定的价值,感兴趣的小伙伴可以了解一下
    2021-12-12
  • Qt获取git版本信息的具体方法

    Qt获取git版本信息的具体方法

    这篇文章主要介绍了Qt获取git版本信息的具体方法,今天又碰到这个问题了,想根据具体的git版本信息做代码问题确认,文中有详细的解决方案,具有一定的参考价值,需要的朋友可以参考下
    2024-04-04
  • 浅析char 指针变量char *=p 这个语句的输出问题

    浅析char 指针变量char *=p 这个语句的输出问题

    下面小编就为大家带来一篇浅析char 指针变量char *=p 这个语句的输出问题。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-05-05
  • C++ 二叉搜索树(BST)的实现方法

    C++ 二叉搜索树(BST)的实现方法

    这篇文章主要介绍了C++ 二叉搜索树(BST)的实现方法,非常不错,具有参考借鉴价值,需要的的朋友参考下
    2017-04-04
  • C++三体星战小游戏源代码

    C++三体星战小游戏源代码

    这篇文章主要给大家介绍了关于C++三体星战小游戏的相关资料,文中给出了详细完整的代码示例,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • C语言函数栈帧的创建与销毁详解

    C语言函数栈帧的创建与销毁详解

    函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,下面这篇文章主要给大家介绍了关于C语言函数栈帧的创建与销毁的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • C语言深入探究自定义类型之结构体与枚举及联合

    C语言深入探究自定义类型之结构体与枚举及联合

    今天我们来学习一下自定义类型,自定义类型包括结构体、枚举、联合体,小编觉得挺不错的,现在就分享给大家,也给大家做个参考
    2022-05-05
  • 浅析设计模式中的代理模式在C++编程中的运用

    浅析设计模式中的代理模式在C++编程中的运用

    这篇文章主要介绍了设计模式中的代理模式在C++编程中的运用,代理模式最大的好处就是实现了逻辑和实现的彻底解耦,需要的朋友可以参考下
    2016-03-03

最新评论