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;
    }
};
相关推荐
caimouse3 小时前
reactos编码规范
c语言·开发语言
小雨下雨的雨4 小时前
井字棋AI机器人实现详解 - Minimax算法实战-鸿蒙PC Electron框架完成
前端·人工智能·算法·华为·electron·鸿蒙
xieliyu.7 小时前
Java算法精讲:双指针(三)
java·开发语言·算法
一条小锦吕*7 小时前
基于Spring Boot + 数据可视化 + 协同过滤算法的推荐系统设计与实现(源码+论文+部署全讲解)
spring boot·算法·信息可视化
CryptoPP8 小时前
快速对接东京证券交易所API数据:实战指南与代码示例
开发语言·人工智能·windows·python·信息可视化·区块链
ZC跨境爬虫8 小时前
跟着 MDN 学JavaScript day_7:数学运算与逻辑判断实战测试
开发语言·前端·javascript·学习·ecmascript
如竟没有火炬9 小时前
最大矩阵——单调栈
数据结构·python·线性代数·算法·leetcode·矩阵
阳区欠9 小时前
【LangChain】LLM基础介绍
开发语言·python·langchain
Jinkxs9 小时前
Java 跨域14-Java 与区块链(Hyperledger)集成
java·开发语言·区块链
8Qi89 小时前
LeetCode 1143 & 718:最长公共子序列 / 最长重复子数组
算法·leetcode·职场和发展·动态规划