C++算法(4)前缀和

1.一维前缀和【模板】

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

暴力解法的时间复杂度为

前缀和:快速求出数组中某一个连续区间的和,时间复杂度是

step1:预处理出一个前缀和数组

dpi表示1,i区间内所有元素的和,dp无需遍历求和,dpi=dpi-1+arri,dp0=0;

索引从1开始是为了避免处理边界情况

step2:使用前缀和数组

l,r之间元素的和,用dpr-dpl-1

代码如下:

cpp 复制代码
#include <iostream>
using namespace std;
#include <vector>
int main() 
{
    int n,m;
    int l,r;
    cin>>n>>m;
    vector<int> arr(n+1);
    for(int i=1;i<n+1;i++)
    {
        cin>>arr[i];
    }
    vector<long long> dp(n+1);//防止溢出
    for(int i=1;i<n+1;i++)
    {
        dp[i]=dp[i-1]+arr[i];
    }
    while(m)
    {
        cin>>l>>r;
        cout<<dp[r]-dp[l-1]<<endl;
        m--;
    }
    return 0;
}

2.二维前缀和【模板】

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

暴力解法的时间复杂度是

前缀和的时间复杂度是

step1预处理出一个前缀和矩阵:

dij表示从1,1i,j这段区间内所有元素的和

dpij=A+B+C+D=++A+B++ +++A+C+++D-A=dpi-1j+dpij-1+arrij-dpi-1j-1

step2使用前缀和矩阵

x1,y1\]\~\[x2,y2

D=A+B+C+D-(A+B)-(A+C)+A=dpx2y2-dpx1-1y2-dpx2y1-1+dpx1-1y1-1

代码如下:

cpp 复制代码
#include <iostream>
using namespace std;
#include <vector>
int main() 
{
    int n,m,q;
    cin>>n>>m>>q;
    int x1,y1,x2,y2;
    vector<vector<long long>> arr(n+1,vector<long long>(m+1));
    vector<vector<long long>> dp(n+1,vector<long long>(m+1));
    for(int i=1;i<n+1;i++)
    {
        for(int j=1;j<m+1;j++)
        {
            cin>>arr[i][j];
        }
    }
    for(int i=1;i<n+1;i++)
    {
        for(int j=1;j<m+1;j++)
        {
            dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1]+arr[i][j];
        }
    }
    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;
        q--;
    }
    return 0;
   
}

3.寻找数组的中心下标

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

f:前缀和数组fi表示0,i-1区间内所有元素的和 fi=fi-1+numsi-1

g:后缀和数组:gi表示i+1,n-1区间所有元素的和gi=gi+1+numsi+1

细节问题:f(0)=0,从左向右,g(n-1)=0,从右向左

代码如下:

cpp 复制代码
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;      
    }
};

还有另一种方法:

cpp 复制代码
class Solution {
public:
    int pivotIndex(vector<int>& nums) 
    {
        int n=nums.size();
        vector<int> dp(n+1);
        for(int i=1;i<n+1;i++)
        {
            dp[i]=dp[i-1]+nums[i-1];
        }
        for(int i=1;i<n+1;i++)
        {
            if(dp[i-1]==(dp[n]-dp[i]))
            {
                return i-1;
            }
        }
        return -1;      
    }
};

4.除自身以外数组的乘积

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

fi表示0,i-1区间内所有元素的乘积,fi=fi-1*numsi-1,f0=1 ,gi表示i+1,n-1区间内所有元素的乘积,gi=gi+1*numsi+1,gn-1=1

代码如下:

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

5.和为K的子数组

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

不能用滑动窗口,因为这里的数组元素包括0和负数,找到一个区间,而right不往后退,若中间还有一个区间会被漏掉。

要在以i位置为结尾的所有子数组中找和为K的,即在0,i-1区间内,有多少个前缀和等于sumi-k(用哈希表储存<int,int>),如果sumi-k==0,就会去找前缀和为0的数组,但0,-1区间不存在,所以要提前把hash0=1;

用一个变量sum来标记前一个位置的前缀和。

如果sum-k这个值在哈希表中存在,说明之前有hashsum-k个前缀和等于sum-k。

代码如下:

cpp 复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) 
    {
        int n=nums.size();
        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;
    }
};

6.和可被K整除的子数组

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

①同余定理:

(a-b)/p=k......0->a%p=b%p

(a-b)/p=k a=b+pk a%p=(b+pk)%p=b%p
②负数%正数的结果及修正:

负%正=负 a%p+p=(a%p+p)%p

如果不修正,会认为4%5和-1%5是两个不同的余数,实际上是模5共余的(-1+5=4)

(sum-x)%k=0->sum%k=x%k,sum%k要修正为(sum%k+k)%k

代码如下:

cpp 复制代码
class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) 
    {
        unordered_map<int,int> hash;
        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;
    }
};

7.连续数组

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

可以把所有0改成-1,在数组中找出最长的子数组,使子数组中所有元素的和为0

如果前一个已经是sumi,后一个必定短于前一个,不用记录。

每次循环(每到一个i),检查一下哈希表中是否有此前缀和,如果有,更新结果,且不用更新哈希表中此前缀和的下标,如果没有,更新哈希表中此前缀和的下标

sumi的最大范围是0,i-1,当它为0,-1时情况也应是存在的,所以手动给hash0=-1;

len=i-下标

代码如下:

cpp 复制代码
class Solution {
public:
    int findMaxLength(vector<int>& nums) 
    {
        unordered_map<int,int> hash;
        int ret=0,sum=0;
        hash[0]=-1;
        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;
    }
};

8.矩阵区域和

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

首先需要一个dp数组,填充时注意下标的映射关系。

但经过k的扩展之后有可能越出m×n矩阵,题目要求的是(x1,y1)和(x2,y2)之间元素的和,且这些元素都要在mat内。

+1是为了在使用时满足映射关系。

代码如下:

cpp 复制代码
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));
        vector<vector<int>> answer(m,vector<int>(n));
        for(int i=1;i<m+1;i++)
        for(int j=1;j<n+1;j++)
        {
            dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1]+mat[i-1][j-1];
        }
        for(int i=0;i<m;i++)
        for(int j=0;j<n;j++)
        {
            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;
            answer[i][j]=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1];
        }
        return answer;
    }
};
相关推荐
AI小老六7 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术8 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize8 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考1 天前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
To_OC2 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC2 天前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode
BadBadBad__AK2 天前
线段树维护区间 k 次方和
c++·数学·算法·stl