C++算法(4)前缀和

1.一维前缀和【模板】

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

暴力解法的时间复杂度为

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

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

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

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

step2:使用前缀和数组

求[l,r]之间元素的和,用dp[r]-dp[l-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预处理出一个前缀和矩阵:

d[i][j]表示从[1,1]到[i,j]这段区间内所有元素的和

dp[i][j]=A+B+C+D=++A+B++ +++A+C+++D-A=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1]

step2使用前缀和矩阵

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

D=A+B+C+D-(A+B)-(A+C)+A=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-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:前缀和数组f[i]表示[0,i-1]区间内所有元素的和 f[i]=f[i-1]+nums[i-1]

g:后缀和数组:g[i]表示[i+1,n-1]区间所有元素的和g[i]=g[i+1]+nums[i+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)

f[i]表示[0,i-1]区间内所有元素的乘积,f[i]=f[i-1]*nums[i-1],f[0]=1 ,g[i]表示[i+1,n-1]区间内所有元素的乘积,g[i]=g[i+1]*nums[i+1],g[n-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]区间内,有多少个前缀和等于sum[i]-k(用哈希表储存<int,int>),如果sum[i]-k==0,就会去找前缀和为0的数组,但[0,-1]区间不存在,所以要提前把hash[0]=1;

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

如果sum-k这个值在哈希表中存在,说明之前有hash[sum-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

如果前一个已经是sum[i],后一个必定短于前一个,不用记录。

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

sum[i]的最大范围是[0,i-1],当它为[0,-1]时情况也应是存在的,所以手动给hash[0]=-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;
    }
};
相关推荐
harder32113 分钟前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
.54821 分钟前
## Sorting(排序算法)
python·算法·排序算法
jinanwuhuaguo24 分钟前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社26 分钟前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
wuweijianlove1 小时前
算法的平均复杂度建模与性能回归分析的技术7
算法·数据挖掘·回归
子琦啊1 小时前
【算法复习】字符串 | 两个底层直觉,吃透高频题
linux·运维·算法
徐某人..1 小时前
基于i.MX6ULL平台的智能网关系统开发
arm开发·c++·单片机·qt·物联网·学习·arm
无敌秋2 小时前
# C++ 简单工厂模式实战指南
c++·简单工厂模式
淘矿人2 小时前
从0到1:用Claude启动你的第一个项目
开发语言·人工智能·git·python·github·php·pygame