【C++】前缀和算法专题

news2024/12/24 10:15:28

目录

介绍

【模版】一维前缀和

算法思路:

代码实现

【模版】二维前缀和

算法思路

代码实现

寻找数组中心的下标

算法思路

代码实现

总结

除自身以外数组的乘积

算法思路

代码实现

和为K的子数组

算法思路

代码实现

和可被整除的K的子数组

算法思想

代码实现

连续数组

算法思想

代码实现

总结

矩阵区域和

算法思想

代码实现

总结


介绍

前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。 

 

【模版】一维前缀和

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

算法思路:

a. 先预处理出来⼀个「前缀和」数组:
dp[i] 表⽰: [1, i] 区间内所有元素的和,那么 dp[i - 1] ⾥⾯存的就是 [1,i - 1] 区间内所有元素的和,那么:可得递推公式: dp[i] = dp[i - 1] +arr[i] ;
b. 使⽤前缀和数组,「快速」求出「某⼀个区间内」所有元素的和:当询问的区间是 [l, r] 时:区间内所有元素的和为: dp[r] - dp[l - 1]

代码实现

#include<iostream>
#include<vector>
using namespace std;


int main()
{
    int n;cin>>n;
    vector<int>s(n+1);
    int q=0;cin>>q;
    for(int i=1;i<=n;i++)cin>>s[i];
    vector<long long>dp(n+1);//防止数据溢出
    for(int i=0;i<=n;i++)
    dp[i]=dp[i-1]+s[i];
    while(q--)
    {
        int r=0,l=0;
        cin>>r>>l;
        cout<<dp[l]-dp[r-1]<<endl;
    }
    return 0;
}

【模版】二维前缀和

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

算法思路

类⽐于⼀维数组的形式,如果我们能处理出来从 [0, 0] 位置到 [i, j] 位置这⽚区域内所有
元素的累加和,就可以在 O(1) 的时间内,搞定矩阵内任意区域内所有元素的累加和。因此我们接下来仅需完成两步即可:
第⼀步:搞出来前缀和矩阵
这⾥就要⽤到⼀维数组⾥⾯的拓展知识,我们要在矩阵的最上⾯和最左边添加上⼀⾏和⼀列0,这样我们就可以省去⾮常多的边界条件的处理(同学们可以⾃⾏尝试直接搞出来前缀和矩阵,边界条件的处理会让你崩溃的)。处理后的矩阵就像这样:
这样,我们填写前缀和矩阵数组的时候,下标直接从 1 开始,能⼤胆使⽤ i - 1 , j - 1 位置的值。
注意 dp 表与原数组 matrix 内的元素的映射关系:
i. dp 表到 matrix 矩阵,横纵坐标减⼀;
ii. matrix 矩阵到 dp 表,横纵坐标加⼀。
前缀和矩阵中 sum[i][j] 的含义,以及如何递推⼆维前缀和⽅程
a. sum[i][j] 的含义:
sum[i][j] 表⽰,从 [0, 0] 位置到 [i, j] 位置这段区域内,所有元素的累加和。对应下图的红⾊区域。

代码实现

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,m,q;
    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];

    //预处理二维矩阵
    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];

    while(q--)
    {
        int x1,y1,x2,y2;
        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;
}

寻找数组中心的下标

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

算法思路

从中⼼下标的定义可知,除中⼼下标的元素外,该元素左边的「前缀和」等于该元素右边的「后缀
和」。
因此,我们可以先预处理出来两个数组,⼀个表⽰前缀和,另⼀个表⽰后缀和。
然后,我们可以⽤⼀个 for 循环枚举可能的中⼼下标,判断每⼀个位置的「前缀和」以及「后缀和」,如果⼆者相等,就返回当前下标。

代码实现

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;
    }
};

总结

此类方法命名为前缀和,并非仅仅是“前”缀和,也有可能是后缀和,与其将其看作是模版,不如将其理解为一种思想,灵活运用。

除自身以外数组的乘积

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

算法思路

注意题⽬的要求,不能使⽤除法,并且要在 O(N) 的时间复杂度内完成该题。那么我们就不能使
⽤暴⼒的解法,以及求出整个数组的乘积,然后除以单个元素的⽅法。
继续分析,根据题意,对于每⼀个位置的最终结果 ret[i] ,它是由两部分组成的:
i. nums[0] * nums[1] * nums[2] * ... * nums[i - 1]
ii. nums[i + 1] * nums[i + 2] * ... * nums[n - 1]
于是,我们可以利⽤前缀和的思想,使⽤两个数组 post 和 suf,分别处理出来两个信息:
i. post 表⽰:i 位置之前的所有元素,即 [0, i - 1] 区间内所有元素的前缀乘积,
ii. suf 表⽰: i 位置之后的所有元素,即 [i + 1, n - 1] 区间内所有元素的后缀乘积,然后再处理最终结果。

代码实现

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

和为K的子数组

和为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 。因此,我们仅需⽤⼀个哈希表,⼀边求当前位置的前缀和,⼀边存下之前每⼀种前缀和出现的次数。

代码实现

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        map<int,int>hash;
        int sum=0;
        int ret=0;hash[0]=1;
        for(auto x:nums)
        {
            sum+=x;//求出前缀和
            if(hash[sum-k])ret+=hash[sum-k];//每次sum都是已知量,又推出的关系我们可以确定每次前一部分的序列也是已知量;
            hash[sum]++;
        }
        return ret;
    }
};

和可被整除的K的子数组

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

算法思想

本题需要的前置知识:
同余定理
如果 (a - b) % n == 0 ,那么我们可以得到⼀个结论: a % n == b % n 。⽤⽂字叙述就是,如果两个数相减的差能被 n 整除,那么这两个数对 n 取模的结果相同。
例如: (26 - 2) % 12 == 0 ,那么 26 % 12 == 2 % 12 == 2
c++ 中负数取模的结果,以及如何修正「负数取模」的结果
a. c++ 中关于负数的取模运算,结果是「把负数当成正数,取模之后的结果加上⼀个负号」。例如: -1 % 3 = -(1 % 3) = -1
b. 因为有负数,为了防⽌发⽣「出现负数」的结果,以 (a % n + n) % n 的形式输出保证为正。
例如: -1 % 3 = (-1 % 3 + 3) % 3 = 2。
思路与 560. 和为 K 的⼦数组 这道题的思路相似。
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 的即可。
我们不⽤真的初始化⼀个前缀和数组,因为我们只关⼼在 i 位置之前,有多少个前缀和等于sum[i] - k 。因此,我们仅需⽤⼀个哈希表,⼀边求当前位置的前缀和,⼀边存下之前每⼀种前缀和出现的次数。

代码实现

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        int ret=0;
        map<int,int>hash;
        hash[0]=1;
        int sum=0;
        for(auto n:nums)
        {
            sum+=n;
            int r=(sum%k+k)%k;
            if(hash.count(r))ret+=hash[r];
            hash[r]++;
        }
        return ret;
    }
};

连续数组

连续数组. - 力扣(LeetCode)

算法思想

稍微转化⼀下题⽬,就会变成我们熟悉的题:
本题让我们找出⼀段连续的区间, 0 1 出现的次数相同。
如果将 0 记为 -1 1 记为 1 ,问题就变成了找出⼀段区间,这段区间的和等于 0
于是,就和 560. 和为 K 的⼦数组 这道题的思路⼀样
i 为数组中的任意位置,⽤ sum[i] 表⽰ [0, i] 区间内所有元素的和。
想知道最⼤的「以 i 为结尾的和为 0 的⼦数组」,就要找到从左往右第⼀个 x1 使得 [x1, i]区间内的所有元素的和为 0 。那么 [0, x1 - 1] 区间内的和是不是就是 sum[i] 了。于是问题就变成:
找到在 [0, i - 1] 区间内,第⼀次出现 sum[i] 的位置即可。
我们不⽤真的初始化⼀个前缀和数组,因为我们只关⼼在 i 位置之前,第⼀个前缀和等于 sum[i]的位置。因此,我们仅需⽤⼀个哈希表,⼀边求当前位置的前缀和,⼀边记录第⼀次出现该前缀和的
位置。

代码实现

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        int n=nums.size();
        int sum=0;
        map<int,int>hash;
        hash[0]=-1;
        int ret=0;
        for(int i=0;i<nums.size();i++)
        {
            sum+=nums[i]==0?-1:1;
            if(hash.count(sum))ret=max(ret,i-hash[sum]);//如果满足条件就不需要更新左端点
            else
            hash[sum]=i;//如果不符合时就需要不断地更新左端点
        }
        return ret;
    }
};

总结

上面的这4道题,又很高的相似性,都是在一段序列中,后一部分是连续的,而前面的一部分与整个序列的和具有一定的关系,每次我们求出的整个序列的sum都是已知的,通过退出的前一段序列的前缀和与整个数组的前缀和关系,可以得到前一段序列的前缀和,比如sum-k,sum%k...我们只需要每次查找有多少个满足条件的子序列即可;

矩阵区域和

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

算法思想

⼆维前缀和的简单应⽤题,关键就是我们在填写结果矩阵的时候,要找到原矩阵对应区域的「左上
⻆」以及「右下⻆」的坐标(推荐⼤家画图)左上⻆坐标: x1 = i - k y1 = j - k ,但是由于会「超过矩阵」的范围,因此需要对 0取⼀个 max 。因此修正后的坐标为: x1 = max(0, i - k), y1 = max(0, j - k) ;
右下⻆坐标: x1 = i + k y1 = j + k ,但是由于会「超过矩阵」的范围,因此需要对 m- 1 ,以及 n - 1 取⼀个 min 。因此修正后的坐标为: x2 = min(m - 1, i + k), y2 = min(n - 1, j + k) 。
然后将求出来的坐标代⼊到「⼆维前缀和矩阵」的计算公式上即可~(但是要注意下标的映射系)。

代码实现

class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        int m=mat.size();//行
        int 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++)
            {
                //下标映射到dp数组
                int x1=max(0,i-k)+1;
                int y1=max(0,j-k)+1;
                int x2=min(m-1,i+k)+1;
                int 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];//在dp矩阵中计算
            }
        }
        return ret;

    }
};

总结

二维矩阵和的索引必须是从{1,1,}开始,平时遇到的题中有时候可能会从{0,0}开始,这时候我们就通过映射进行转换,然后使用模版化的二维矩阵进行处理数据;

这道题中处理越界问题的操作值得借鉴,通过max和min函数来更加方便的进行下标的处理;

另外就是二维模版的公式,不需要死记硬背,每次使用时只需要随手画一个图就能轻松写出;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1965367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 操作Git仓库

代码 #include "common.h" #include "args.c" #include "common.c"enum index_mode {INDEX_NONE,INDEX_ADD };struct index_options {int dry_run;int verbose;git_repository* repo;enum index_mode mode;int add_update; };/* Forward declar…

Python零基础详细入门教程

Python零基础详细入门教程可以从以下几个方面展开&#xff0c;帮助初学者系统地学习Python编程&#xff1a; 一、Python基础入门 1. Python简介 Python的由来与发展&#xff1a;Python是一种广泛使用的高级编程语言&#xff0c;以其简洁的语法和强大的功能而受到开发者的喜爱…

2024第二十届中国国际粮油产品及设备技术展示交易会

2024第二十届中国国际粮油产品及设备技术展示交易会 时间&#xff1a;2024年11月15-17日 地点&#xff1a; 南昌绿地国际博览中心 展会介绍&#xff1a; 随着国家逐年加大对农业的投入&#xff0c;调整农业产业结构&#xff0c;提高农产品附加值&#xff0c;促进农民增收。…

CRMEB-众邦科技 使用笔记

1.启动项目报错 Unable to load authentication plugin ‘caching_sha2_password’. 参考&#xff1a;http://t.csdnimg.cn/5EqaE 解决办法&#xff1a;升级mysql驱动 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</ar…

超级弱口令检查工具

一、背景 弱口令问题主要源于用户和管理员的安全意识不足&#xff0c;以及为了方便记忆而采用简单易记的密码。这些密码往往仅包含简单的数字和字母&#xff0c;缺乏复杂性和多样性&#xff0c;因此极易被破解。弱口令的存在严重威胁到系统和用户的数据安全&#xff0c;使得攻击…

在局域网中的另一台主机如何访问windows10WSL中的服务

文章目录 1&#xff0c;开启win10 路由功能2&#xff0c;配置转发规则 1&#xff0c;开启win10 路由功能 2&#xff0c;配置转发规则 netsh advfirewall firewall add rule name"Allowing LAN connections" dirin actionallow protocolTCP localport80 netsh interf…

计算机体系结构:缓存一致性ESI

集中式缓存处理器结构&#xff08;SMP&#xff09; 不同核访问存储器时间相同。 分布式缓存处理器结构&#xff08;NUMA&#xff09; 共享存储器按模块分散在各处理器附近&#xff0c;处理器访问本地存储器和远程存储器的延迟不同&#xff0c;共享数据可进入处理器私有高速缓存…

程序员自曝接单:三年时间接了25个单子,收入12万

程序员接单在程序员的副业中并不少见。程序员接单作为一个起步快、门槛低、类型多样的副业选择&#xff0c;一直深受程序员的青睐。就算你没有接触过接单&#xff0c;也一定对接单有过了解。 程序员接单是指程序员通过接取开发者发布的项目或任务来获取收入的一种工作方式。程序…

“八股文”的江湖:助力、阻力还是空谈?深度解析程序员面试的敲门砖

一、引言&#xff1a;八股文的江湖——助力、阻力还是空谈&#xff1f; 1.1 八股文的定义与背景 八股文&#xff0c;原指我国明清时期科举考试的一种应试文体&#xff0c;因其固定模式和空洞内容而备受诟病。在当今的程序员面试中&#xff0c;程序员的“八股文”通常指的是在技…

告别手动操作:这个微信自动化工具你一定要试试!

随着科技的发展&#xff0c;越来越多的自动化工具应运而生&#xff0c;帮助我们轻松管理微信号。 今天&#xff0c;就给大家揭开这个能让微信自动化的工具的神秘面纱&#xff0c;看看它们能为我们的工作带来哪些便利。 1、批量自动加好友 通过个微管理系统&#xff0c;你可以…

【Unity】 HTFramework框架(五十四)Deployment 轻量级资源部署管线

更新日期&#xff1a;2024年7月31日。 Github源码&#xff1a;[点我获取源码] 索引 Deployment 轻量级资源部署管线使用 Deployment一、创建部署配置二、编辑部署配置三、正式开始资源部署步骤一&#xff1a;资源打包步骤二&#xff1a;资源版本构建步骤三&#xff1a;资源版本…

Redis 初步认识

目录 1. 概述 2. 数据结构 3. 使用方式 4. 优势 1. 概述 Redis &#xff08;remote directory server&#xff09;是一个开源的基于内存的数据存储系统&#xff1b; 可以用作数据库缓存和消息队列等各种场景&#xff0c;也是目前最热门的 NoSQL 数据库之一&#xff1b; 早…

java算法递归算法练习-数组之和

简单找个题目练习一下递归算法&#xff0c;输入一组数组&#xff0c;使用递归的方法计算数组之和。其实这个题目&#xff0c;用循环的方式也很简单就能解决&#xff0c;直接循环遍历一下相加就行了&#xff0c;但是我们用来练习一下递归。 先来找基线条件和递归条件 基线条件…

Stable Diffusion AI 绘画 之 ControlNet 插件及其对应模型的下载安装

一、介绍 ControlNet: ControlNet是一种通过添加附加条件来控制扩散模型&#xff08;例如Stable Diffusion&#xff09;的神经网络结构。一般的使用方法是结合Stable Diffusion来做到精准控图。 通过上面的方式&#xff0c;ControlNet 可以重用SD编码器作为一个深度&#xff0…

企业版邮箱如何提升效率

企业邮箱是如何提升企业效率的呢&#xff1f;企业版邮箱通过专业形象、安全性、稳定性、集成能力等优势提升效率&#xff0c;支持高效内部沟通、团队协作、客户关系管理、安全性与合规性&#xff0c;并支持远程工作&#xff0c;是企业必备的高效工具。 一、企业版邮箱的基本功…

【通俗理解】神经网络动力学——从梯度下降到拓扑结构的桥梁

【通俗理解】神经网络动力学——从梯度下降到拓扑结构的桥梁 神经网络与动力学的类比 你可以把神经网络看作是一个“城市”&#xff0c;其中的神经元是“居民”&#xff0c;他们通过连接&#xff08;道路&#xff09;交互。而动力学则是一个“交通分析师”&#xff0c;它研究居…

【MySQL】事务 【下】{重点了解读-写 4个记录隐藏列字段 undo log日志 模拟MVCC Read View sel}

文章目录 1.MVCC数据库并发的场景重点了解 读-写4个记录隐藏列字段 2.理解事务undo log日志mysql日志简介 模拟MVCC 3.Read Viewselect lock in share modeMVCC流程RR与RC 1.MVCC MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09;是…

typora简单使用教程

一、下载安装typora Typora是一款流行的Markdown文本编辑器&#xff0c;作者是按照这篇文章完成了typora的下载和安装的&#xff1a;http://t.csdnimg.cn/D2U1U 二、偏好设置 进入界面&#xff0c;点击“文件”&#xff0c;点击“偏好设置”。 三、基本使用 作者是跟着这个…

如何解决 hCaptcha:全面指南

hCaptcha 是一种注重隐私的验证码服务&#xff0c;因其有效性和独特的挑战而广受欢迎。值得注意的是&#xff0c;hCaptcha 具有一定的技术复杂性。本指南将探讨 hCaptcha 的特性&#xff0c;并提供自动化解决方法。 什么是 hCaptcha 及其挑战 hCaptcha 以其基于图像的挑战而闻…

mysql--表的基本操作(curd)

一&#xff0c;表的创建 语法&#xff1a; 如果创建表没有指定字符集存储引擎&#xff0c;默认是继承表所在的数据库的。 修改表的字段 &#xff08;1&#xff09;增加 ALTER TABLE tablename ADD (column datatype [DEFAULT expr][,column datatype]...); &#xff08;2&am…