目录
一、前缀和的定义
二、一维前缀和
三、一维前缀和OJ题
3.1、前缀和
3.2、寻找数组中心下标
3.3、除自身以外数组的乘积
3.4、和为K的数组
3.5、和可被K整除的子数组
3.6、连续数组
四、二位前缀和
4.1、二维前缀和
4.2、矩阵区域和
一、前缀和的定义
对于一个给定的数列A,他的前缀和数中 S 中 S[ i ] 表示从第一个元素到第 i 个元素的总和。
如下图:绿色区域的和就是前缀和数组中的 S [ 6 ]。
这里我们需要注意的是:前6个数的和为什么是S【6】呢?数组第6个数下标不应该是5吗?
是的,我们在下表面推导公式会讲到这个问题。
二、一维前缀和
前缀和数组的每一项是可以通过原序列以递推的方式推出来的,递推公式就是:S[ i ] = S[ i - 1 ] + A[ i ]。S[ i - 1 ] 表示前 i - 1 个元素的和,在这基础上加上 A[ i ],就得到了前 i 个元素的和 S [ i ]。
当我们要求的是序列 A 的前 n 个数之和时,如果我们是从下标为 0 的位置开始存储前缀和数组,此公式:sum = S[ r ] - S[ l - 1 ] 显然就无法使用了,为了是这个公式适用于所有情况,我们将从下标为 1 的位置开始存储前缀和,并且将下标为 0 的位置初始化为 0。
三、一维前缀和OJ题
3.1、前缀和
【模板】前缀和_牛客题霸_牛客网 (nowcoder.com)
题目描述:
算法思路:
#include <iostream>
#include<vector>
using namespace std;int main()
{int n,q;cin>>n>>q;vector<int> arr(n+1,0);for(int i=1;i<=n;i++) cin>>arr[i];vector<long long> dp(n+1,0);for(int i=1;i<=n;i++) dp[i]=arr[i]+dp[i-1];int l,r;while(q--){cin>>l>>r;cout<<dp[r]-dp[l-1]<<endl;}return 0;
}
3.2、寻找数组中心下标
724. 寻找数组的中心下标 - 力扣(LeetCode)
题目描述:
算法思路:
class Solution {
public:int pivotIndex(vector<int>& nums) {int n=nums.size();vector<int> f(n),g(n);//前缀和for(int i=1;i<n;i++)f[i]=nums[i-1]+f[i-1];//后缀和for(int i=n-2;i>=0;i--)g[i]=nums[i+1]+g[i+1];for(int i=0;i<n;i++){if(g[i]==f[i])return i;}return -1;}
};
3.3、除自身以外数组的乘积
238. 除自身以外数组的乘积 - 力扣(LeetCode)
题目描述:
算法思路:
class Solution {
public:vector<int> productExceptSelf(vector<int>& nums) {int n=nums.size();vector<int> g(n),f(n);//前缀积f[0]=g[n-1]=1;for(int i=1;i<n;i++)f[i]=f[i-1]*nums[i-1];//后缀积for(int i=n-2;i>=0;i--)g[i]=g[i+1]*nums[i+1];vector<int> arr(n);for(int i=0;i<n;i++){arr[i]=g[i]*f[i];}return arr;}
};
3.4、和为K的数组
560. 和为 K 的子数组 - 力扣(LeetCode)
题目描述:
算法思路:
代码实现:
class Solution {
public:int subarraySum(vector<int>& nums, int k) {unordered_map<int,int> hash;int sum=0,ret=0;hash[0]=1;for(auto x:nums){sum+=x;if(hash.count(sum-k)){ret+=hash[sum-k];}hash[sum]++;}return ret;}
};
3.5、和可被K整除的子数组
974. 和可被 K 整除的子数组 - 力扣(LeetCode)
题目描述:
算法思路:
class Solution {
public:int subarraysDivByK(vector<int>& nums, int k) {unordered_map<int,int> hash; //第一个int存余数,第二个存个数int sum=0,ret=0;hash[0%k]=1; for(auto x:nums){sum+=x;int r=(sum%k+k)%k;if(hash.count(r)) ret+=hash[r];hash[r]++;}return ret;}
};
3.6、连续数组
525. 连续数组 - 力扣(LeetCode)
题目描述:
class Solution {
public:int findMaxLength(vector<int>& nums) {unordered_map<int,int> hash;hash[0]=-1;int sum=0,ret=0;for(int i=0;i<nums.size();i++){sum+=nums[i]==0?-1:1;//当前位置的前缀和,将0变-1if(hash.count(sum)) ret=max(ret,i-hash[sum]);else hash[sum]=i;}return ret;}
};
四、二位前缀和
例如:对与 x = 4,y = 3 这么一组输入,就是将原矩阵序列中蓝色区域的元素相加,得到的结果便是前缀和矩阵S中 S[ 4 ][ 3 ] 的值。
例如上图:我们要求蓝色矩阵中所有元素的和。
现在就差最后一步了,怎么求出前缀和矩阵中的每一个值嘞??同理利用递推关系求就阔以啦。
S[ i ][ j ] = S[ i - 1 ][ j ] + S[ i ][ j - 1 ] - S[ i - 1][ j - 1 ] + a[ i ][ j ]
五、二维前缀和OJ题
4.1、二维前缀和
【模板】二维前缀和_牛客题霸_牛客网 (nowcoder.com)
题目描述:
算法思路:
- 首先对矩阵进行预处理,得到对应的前缀和矩阵。
- 利用前缀和矩阵相应区域的加减运算,即可得到对应子矩阵中所有元素的累加和。
图解展示(图中presum[3][4]除了包括绿色部分,还包括其它重叠的部分,其它几项也一样,另外presum[1][1]被多减了一次,所以最后要加一次):
代码实现:
#include <iostream>
#include<vector>
using namespace std;int main()
{int n,m,q;cin>>n>>m>>q;vector<vector<long long>> arr(n+1,vector<long long>(m+1));for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin>>arr[i][j];}}vector<vector<long long>> dp(n+1,vector<long long>(m+1));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,x2,y1,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;}return 0;
}
4.2、矩阵区域和
1314. 矩阵区域和 - 力扣(LeetCode)
题目描述
算法思路:
代码实现:
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>> ret(m,vector<int>(n));for(int i=0;i<m;i++){for(int j=0;j<n;j++){int x1 = max(0, i - k) + 1, y1 = max(0, j - k) + 1;int x2 = min(m - 1, i + k) + 1, y2 = min(n - 1, j + k) + 1;ret[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];}}return ret;}
};