C++中前缀和数组(算法)基本介绍
作者:猫咪-9527
1.前言
如何快速求得一个数组的前缀和?
1. 1 前缀和的基本概念
前缀和(Prefix Sum)是指数组中某个位置之前的所有元素的和。对于数组arr
,其前缀和数组prefixSum
定义为:
prefixSum[0] = 0
prefixSum[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,从而进行判断,减少复杂边界情况
1 2 3 4 5
0 | 1 | 2 | 3 | 4 | 5 |
!!!此步是为了处理边界情况
具体代码如下:
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) 的时间内,搞定矩阵内任意区域内所有元素的累加和。因此我们 接下来仅需完成两步即可:
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 |
0 | 0 | 0 | 0 |
0 | 1 | 2 | 3 |
0 | 4 | 5 | 6 |
0 | 7 | 8 | 9 |
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]坐标的和,即为下图蓝色区域位置
此时我们所求的[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];
将表格抽象为四部分:分别为绿,粉,蓝,白
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
次。 - 初始化一个变量
result
为0
,用于记录和为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++前缀和数组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!