当前位置: 首页 > news >正文

算法篇----前缀和

1.什么是前缀和

前缀和就是快速求出数组中某一个连续区间的和

2.怎么做

一般我们是分为两步:

第一步:预处理出来一个前缀和数组,什么是前缀和数组呢?

前缀和数组就是一个数组,它里面存储的数据是原数组前n个数据的和,这里我们先借用一下动态递归里面的dp

我们规定,dp[i]表示:[1,i]区间内所有元素的和,举个例子,

倘若arr=[1,4,7,2,5,8,3,6,9]

那么dp=[1,5,12,14,19,27,30,36,45]

第二步:使用前缀和数组

3.经典例题

3.1【模板】前缀和

【模板】前缀和_牛客题霸_牛客网
这道题就是对前缀和的理解,我们直接看解题思路吧~
方法一)暴力破解
这种方法没啥说的,就按照题目说的做就行,从头查到尾,但是时间复杂度将会很大,为O(N*q)

方法二)前缀和 

我们还是想上文提到一样,求一下dp,之后题目让我们求区间[l,r]的前缀和,我们就dp[r]-dp[l-1]就好了,原理很简单不证明了。

这里还有个小细节:

下标为什么从1开始计数,原因就在于当我们想查询[0,2]时,那就变成dp[2]-dp[-1]了,那数组下标还有-1吗?肯定就越界了啊!倘若我们想查询[1,2]时,返回的是dp[2]-dp[0],而dp[0]=0,不影响结果!!!

说白了这么做就是为了处理边界情况,通过添加虚拟节点的方式

参考代码:

#include <iostream>
#include<vector>
using namespace std;int main() 
{//读取数据int n,q;cin>>n>>q;vector<int> arr(n+1);for(int i=1;i<=n;i++){cin>>arr[i];}//预处理vector<long long> dp(n+1);for(int i=1;i<=n;i++) dp[i]=dp[i-1]+arr[i];//使用前缀和数组int l=0,r=0;while(q--){cin>>l>>r;cout<<dp[r]-dp[l-1]<<endl;}return 0;
}

3.2 【模板】⼆维前缀和

【模板】二维前缀和_牛客题霸_牛客网

这道题要求我们输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和

解题思路:

方法一)暴力破解

我们就按照题目说的做就好,正面破解,但是时间复杂度是O(N*M*Q),毫无疑问,铁定超时!
方法二)前缀和

还是跟上一题差不多,这里要预处理出来一个前缀和矩阵,dp[i][j],

其中,dp[i][j]表示从[1,1]到[i.j]这段区间里面所有元素的和

那我们咋求呢?两层for?那搞不好又超时了,那很坏了

这里我们借助一些数学知识:假设要求dp[i][j],我们可以将其分成四块

这样以来,

原式=A+B+C+D

\becauseB和C不太好求,我们转化一下

\therefore 原式=A+B  +   A+C   +D  -A

           =dp[i-1][j] + dp[i][j-1] +arr [i][j] - dp[i-1][j-1]

好!基础措施做完了~

第二步应该就是使用前缀和矩阵了,题目让我们求[x1,y1]~[x2,y2]的

我们可以利用前面求出的矩阵,还是先画个图:

所以可以推导出解题公式了:

 之后把公式往代码里面套就好啦~

参考代码:

#include <iostream>
#include <vector>
using namespace std;int main() 
{//1.读入数据int n=0,m=0,q=0;cin>>n>>m>>q;vector<vector<int>> arr(n+1,vector<int> (m+1));for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin>>arr[i][j];}}//2.预处理前缀和矩阵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];}}//3.使用前缀和矩阵int x1=0,y1=0,x2=0,y2=0;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;
}

3.3 寻找数组的中心下标

724. 寻找数组的中心下标 - 力扣(LeetCode)

解题思路:

方法一)暴力破解

就是从头到尾依次把arr[i]假设为中心,之后遍历看左右两侧是否满足要求,毫无疑问这种方法会超时,时间复杂度为O(N^2)

方法二)前缀和

题目要求的不还是让我们求前N个数的和吗?这样的话我们就可以使用前缀和的算法思想了,不同的是,这里我们要使用两次,如下图所示:

这里我们构造两个和数组,一个是f[i],另一个是g[i]

其中,f表示前缀和数组:f[i]表示区间[0,i-1],所有元素的和,状态方程为f[i]=f[i-1]+arr[i-1]

           g表示后缀和数组:g[i]表示区间[i+1,n-1],所有元素的和,状态方程为g[i]=g[i+1]+arr[i+1]

构造完成后,我们只需要在区间[0,n-1],找到一个下标i,使得f[i]==g[i],即可解决

之后还有几个细节问题,f(0)=0,g(n-1)=0,f在填充时是从左向右的,g在填充时是从右向左的

参考代码:

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]=f[i-1]+nums[i-1];for(int i=n-2;i>=0;i--)g[i]=g[i+1]+nums[i+1];for(int i=0;i<n;i++){if(f[i]==g[i])return i;}return -1;}
};

3.4 除自身以外数组的乘积

238. 除自身以外数组的乘积 - 力扣(LeetCode)

解题思路:

方法一)暴力破解

这种方法时间复杂度为O(N^2)肯定会超时,具体做法跟上面那个题差不多,不多说了!

方法二)前缀积

我们还是将数组分为两段,前面的为f,后面的为g,具体思路跟上一题差不多

我们写一下状态方程:

f[i]表示区间[0,n-1]的乘积,g[i]表示区间[i+1,n-n]的乘积

f[i]=f[i-1]*arr[i-1]

g[i]=g[i-1]*arr[i-1]

处理一下细节问题,f和g的第一个位置都不能为0,否则会导致我们的前缀积数组全是零,为此我们将f(0)=g(n-1)=1即可

本题的返回值就是f[i]*g[i]

参考代码:

class Solution {
public:vector<int> productExceptSelf(vector<int>& nums) {int n=nums.size();vector<int> f(n),g(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> ret(n);for(int i=0;i<n;i++){ret[i]=f[i]*g[i];}return ret;}
};

3.5 和为 k 的子数组

560. 和为 K 的子数组 - 力扣(LeetCode)

解题思路:前缀和+哈希表

i 为数组中的任意位置,用 sum[i] 表示[0, i] 区间内所有元素的和。 想知道有多少个「以 i 为结尾的和为 k 的子数组」,就要找到有多少个起始位置为 x1, x2, x3... 使得 [x, i] 区间内的所有元素的和为 k 。那么 [0, x] 区间内的和是不是就是sum[i] - k 了。于是问题就变成: 找到在 [0, i - 1] 区间内,有多少前缀和等于 sum[i] - k 的即可。

不理解的看一下图:

我们不用真的初始化⼀个前缀和数组,因为我们只关心在 i 位置之前,有多少个前缀和等于
sum[i] - k 。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种
前缀和出现的次数。我们可以设置一个unordered_map<int,int>用于统计,其中,第一个int 表示前缀和,第二个int 表示次数
参考代码:
class Solution {
public:int subarraySum(vector<int>& nums, int k) {unordered_map<int,int> hash;   //统计前缀和出现的次数hash[0]=1;int sum=0,ret=0;for(auto x:nums){sum+=x;  //计算当前位置的前缀和if(hash.count(sum-k))  ret+=hash[sum-k];  //统计个数hash[sum]++;}return ret;}
}

 3.6 和可被 K 整除的子数组

974. 和可被 K 整除的子数组 - 力扣(LeetCode)

解题思路:

前置知识:

1)同余定理
如果 (a - b) % n == 0 ,那么我们可以得到一个结论: a % n == b % n 。用文字叙述就是,如果两个数相减的差能被 n 整除,那么这两个数对 n 取模的结果相同。
例如: (26 - 2) % 12 == 0 ,那么 26 % 12 == 2 % 12 == 2
2)c++ 中负数取模的结果,以及如何修正「负数取模」的结果
c++ 中关于负数的取模运算,结果是「把负数当成正数,取模之后的结果加上一个负号」。
例如: -1 % 3 = -(1 % 3) = -1
因为有负数,为了防止发生「出现负数」的结果,以 (a % n + n) % n 的形式输出保证为正。
例如: -1 % 3 = (-1 % 3 + 3) % 3 = 2

解题思路与上一题相似!

i 为数组中的任意位置,用sum[i] 表示 [0, i] 区间内所有元素的和。 想知道有多少个「以 i 为结尾的可被 k 整除的子数组」,就要找到有多少个起始位置为 x1, x2, x3... 使得 [x, i] 区间内的所有元素的和可被 k 整除。 设 [0, x - 1] 区间内所有元素之和等于 a [0, i] 区间内所有元素的和等于 b ,可得(b - a) % k == 0 。 由同余定理可得, [0, x - 1] 区间与 [0, i] 区间内的前缀和同余。于是问题就变成: 找到在 [0, i - 1] 区间内,有多少前缀和的余数等于 sum[i] % k 的即可。

参考代码:

class Solution {
public:int subarraysDivByK(vector<int>& nums, int k) {unordered_map<int,int> hash;hash[0%k]=1;     //0这个数的余数int sum=0,ret=0;for(auto e:nums){sum+=e;        //计算当前位置的前缀和int r=(sum%k+k)%k;   //修正后的余数if(hash.count(r))  ret+=hash[r];    //统计结果hash[r]++;}return ret;}
};

3.7 连续数组

 525. 连续数组 - 力扣(LeetCode)

解题思路

这道题如果正面破解会很费力,但是倘若我们将0转换为-1,那么问题就会好解决很多,相当于我们直接找前缀和为0的最长子数组就好了!具体代码与前面的代码差不多~,我们还是借助哈希表来解决~hash<int,int>,第一个int 代表前缀和,第二个代表当前的下标

参考代码

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))    //count函数返回当前sum对应的键值对的那个下标,这里的哈希表是 hash<sum,index>{//说明这个前缀和之前就出现过·是存在的,那我们就要更新一下长度ret=max(ret,i-hash[sum]);}else{//说明这个前缀和之前没有出现过,那我们就把它放到哈希表中hash[sum]=i;}}return ret;}
};

3.8 矩阵区域和 

1314. 矩阵区域和 - 力扣(LeetCode)

解题思路

这道题要求我们算出一个矩阵小单元一圈所有元素的和,那我们应该怎么算呢?

我们参考二维前缀和数组模板,先求一下递归公式:

假设我们要求[i][j]位置的一圈的元素和,不难推到出如下公式:

dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mat[i][j]

之后便是使用了,同理写出公式:

 但是那个x1,x2,y1,y2是什么呢?怎么确定呢?

回到题干,假设我们有任意一个位置坐标ans[i][j],我们如何确定x1,x2,y1,y2?由题意并且画个图,知道:
x1=i-k                        y1=j-k

x2=i+k                       y2=j+k

但是万一超出边界了呢?所以我们略改一下:

x1=max(0,i-k)              y1=max(0,j-k)

x2=min(m-1,i+k)          y2=min(n-1,j+k)

但是为了处理好ans(x,y)和dp(x+1,y+1)的关系,我们在刚刚求出的x1,x2,y1,y2都加上1即可!

参考代码:

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];    //这里的前缀和矩阵公式与刚刚讲的略有不同,这里的经过了优化(可以理解为它的下标是从                            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;}
};

前缀和内容结束! 

http://www.dtcms.com/a/304050.html

相关文章:

  • 【前端开发】一. html css js 初印象
  • 验证pyspark提交参数指定环境变量生效
  • 什么情况下会出现数据库和缓存不一致的问题?
  • VS Code编辑器
  • jvm冷门知识十讲
  • Three.js实现银河螺旋星云粒子特效——原理、实现
  • 译 | 介绍PyTabKit:一个试图超越 Scikit-Learn的新机器学习库
  • 基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)
  • JVM指令集
  • LeetCode|Day29|1009. 十进制整数的反码|Python刷题笔记
  • 服装行业SaaS系统有哪些
  • 【C++】指针
  • 基于Coze平台的自动化情报采集与处理引擎—实现小红书图文到飞书的端到端同步
  • 用 Python 轻松实现时间序列预测:Darts 时间序列混合器(TSMixer)Time Series Mixer
  • WAIC 2025观察:昇腾助力AI融入多元化生活场景
  • sqli-labs通关笔记-第25关GET字符注入(过滤or和and 脚本法)
  • 数据手套五指触觉灵巧手遥操作方案
  • Hyperchain安全与隐私机制详解
  • Windows 下使用 Ollama 调试大模型
  • 故障排除---Operator部署Prometheus无法NodePort访问
  • zoho crm为什么xx是deal的关联对象但是调用函数时报错说不是关联对象
  • 译|生存分析Survival Analysis案例入门讲解(一)
  • 电磁兼容(EMC):整改案例(十三)屏蔽外壳开孔解决433MHz无线通信问题
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-45,(知识点:负反馈的作用,基础理解,干扰和噪声的抑制)
  • React--》实现 PDF 文件的预览操作
  • WisFile(文件整理工具) v1.2.19 免费版
  • 自然语言处理NLP(3)
  • Mac m系列芯片安装node14版本使用nvm + Rosetta 2
  • 【第四章:大模型(LLM)】01.神经网络中的 NLP-(3)文本情感分类实战
  • 网络安全运维面试准备