【基础算法总结】前缀和一

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

1.【模板】前缀和

题目链接:DP34 【模板】前缀和

题目描述:

在这里插入图片描述

注意这道题数组下标是从1开始的,因此我们需要申请n+1空间大小,才能到an,就是给个区间让你求这个区间的和。

在这里插入图片描述

算法原理:

解法一:暴力求解
题目说那个区间就去找那个区间,把区间里面的数都加起来。但是这样有个问题,时间复杂度有点大,如果每次区间都是最左端点和最右边端点,一次就是O(N),要循环q次。时间复杂度O(q*n)
在这里插入图片描述

解法二:前缀和

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

  1. 预处理出来一个前缀和数组
  2. 使用前缀和数组

预处理出来一个前缀和数组
申请一个和原数组一样大小的数组。根据下面公式把前缀和数组填写

dp[i]表示 [1,i]区间内所有元素的和
dp[i] = dp[i-1] + arr[i]

在这里插入图片描述

使用前缀和数组
使用前缀和数组就是处理每次询问,如果l=3,r=5,就是让算(3,5)区间,此时不直接求,而只需要间接求出1-5所有元素的和,在再减去1-2的和,不就是3-5的和吗。因为我们数组中每个数对应的是[1,i]的和,因此dp[r]-dr[l-1]就是该区间的和。

[l,r] —> dp[r]-dp[l-1]
在这里插入图片描述
细节问题:为什么我们的下标要从 1 开始计数?

如果从0开始计数,当有一次[0,2]带入dp[r]-dp[l-1] ,你会发现 dp[2]-dp[-1],数组越界。
为了处理边界情况 :初始化添加虚拟节点(辅助节点)
在这里插入图片描述

#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];

    //1.前缀和数组
    vector<long long> dp(n+1);
    for(int i=1;i<=n;++i) dp[i]=dp[i-1]+arr[i];

    //2.使用前缀和
    int l=0,r=0;
    while(q--)
    {
        cin>>l>>r;
        cout<<dp[r]-dp[l-1]<<endl;
    }
    return 0;
}

2.【模板】二维前缀和

题目链接:DP35 【模板】二维前缀和

题目描述:

在这里插入图片描述

注意这道题下标也是从1开始的。让求以(x1,y1)为左上角,(x2,y2)为右上角的子矩阵的和。

在这里插入图片描述

算法原理:

解法一:暴力求解
让求那个区间,就把那个区间的和一一加起来
在这里插入图片描述
但是如果每次都是求最左点和最右点的子矩阵,然后循环q次。最终是时间复杂度O(nmq)

解法二:前缀和

  1. 预处理出来一个前缀和矩阵
  2. 使用前缀和矩阵

在这里插入图片描述
这个子矩阵难道如果自己还要去遍历一次求和吗,那时间复杂度不又高了吗。我们考虑一下把这个区间划分一下,划分成四个区域,如果求这个区间的和,我们就转换成求这个四个区域的和。把这四个区域的和加起来就好了,但是单单求每个区域也不好求,因此我们可以把区域整合一下再求。于是得到一个公式
在这里插入图片描述

这样就不要再一个个遍历。而是只需要一次遍历就可以把前缀和数组填写好。下面就是使用前缀和数组
在这里插入图片描述

这里也和上面的题一样要注意边界情况。总体时间复杂度O(nm)+O(q)

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

int main() {
    // 1. 读入数据
    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];

    // 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.寻找数组的中心下标

题目链接:724. 寻找数组的中心下标

题目描述:

在这里插入图片描述

本题就让让找一个数组的中心下标,如果一个下标左区间的和等于该下标右区间的和,那这个下标就是中心下标。中心下标如果位于最左边认为左测之和等于0,同样右侧之和也应为0。同理中心下标如果位于最右侧认为右侧之和等于0,那左侧之和也应为0。如果不存在中心下标返回-1。如果有多个中心下标返回最左侧的。

算法原理:

解法一:暴力求解

每次给一个i下标,然后把它左侧 0~i-1 和 与 右侧 i+1~n-1和 求出来,然后在判断。但是这样是复杂度是O(N*N)
在这里插入图片描述
因为想判断i这个位置是否是中心下标,我仅需要判断一下 0~i-1 的和,那使用前缀和数组不就正好可以解决某一个连续数据的和吗。同理求 i+1~n-1区间的和,可以把前缀和稍微改一下,改成后缀和。前缀和是从左往后走,那后缀和是倒着走从右往左走

解法二:前缀和

虽然我们前面有前缀和的模板,但是不要死记模板,一定要充分理解思想。然后碰到不同的题,才会不怕题目的特殊要求。前面模板是
dp[i]=dp[i-1]+nums[i],这是它数组下标从1开始,而我们这道题数据下标从0开始,需要特殊处理一下!

因为我们这里需要两个数组,所以就定义一个前缀和数组,一个后缀和数组。

因为求的是i是否是中心下标,所以对于前缀和数组 f[i] 我仅需要 0~ i-1 这段区间所有元素的和,对于后缀和数组 g[i] 仅需要 i+1 ~ n-1 这段区间所有的和。这和前面是不一样的的

在这里插入图片描述

如何根据这个分析,得到递推公式呢?就是把状态标识转化成状态转移方程。

f[i] 表示的是 0 ~ i-1 所有元素的和,那 f[i-1] 不就是 0 ~ i-2 所有元素的和吗,此时在加 上nums[i-1] ,不就是 0~ i-1 所有元素的和吗

g[i] 表示 i+1 ~ n-1 所有元素的和,g[i+1]不就表示 i+2 ~ n-1所有元素的和吗,此时在加上nums[i+1],不就是 i+1 ~ n-1 所有元素的和吗
在这里插入图片描述

前缀和数组+后缀和数组预处理好了,接下来就是使用前缀和数组+后缀和数组了

在这里插入图片描述
但是这里有细节问题,前面的题数组下标是从1开始的。但是今天这道题是从数组0下标开始的,不特殊处理代入公式会有越界风险。并且有一个前缀和数组还有一个后缀和数组,那后缀和数组从哪里开始算呢?

在这里插入图片描述

class Solution {
public:
    int pivotIndex(vector<int>& nums) {

        int n=nums.size();
        vector<int> f(n),g(n);

        //处理细节问题,但是初始时本来数组都是0,因此可以不用在写了
        // f[0]=0;
        // g[n-1]=0;

        //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];

        //2.使用前缀和数组+后缀和数组

        for(int i=0;i<n;++i)
            if(f[i]==g[i]) 
                return i;

        return -1;
    }
};

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

题目链接:238. 除自身以外数组的乘积

题目描述:

在这里插入图片描述
算法原理:

解法一:暴力求解
给一个位置就求除它之外所有位置的乘积。时间复杂度O(N^2)。
在这里插入图片描述

处理一个问题直接解决不太好解决,先预处理一个数组。----> 前缀和思想
,典型的以空间换时间

解法二:前缀和

记住前缀和是一个思想,千万不要死记模板,记得应该是思想!

在这里插入图片描述

然后根据状态标识得到一个状态转移方式

要求 0 ~ i-1 区间所有元素的积,说明已经知道 0 ~ i-2 区间所有元素的积,
0 ~ i-2 区间所有元素的积在乘一个nums[i-1] 不就是 0 ~ i-1 区间所有元素的积了嘛,而0 ~ i-2 区间所有元素的积是 f[i-1] ,所以前缀积方式就有了。同理后缀也是一样的。

在这里插入图片描述

现在预处理好了,接下来就是使用的问题了
直接申请一个和原数组大小,数组中填的是 f[i]*g[i]

在这里插入图片描述

这里依然有细节问题,注意越界问题,并且不能给0,不然乘积全都是0。

在这里插入图片描述

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {

        int n=nums.size();
        vector<int> f(n),g(n),ret(n);

        // 处理细节
        f[0]=g[n-1]=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];

        // 2.使用
        for(int i=0;i<n;++i)
            ret[i]=f[i]*g[i];

        return ret;

    }
};
  • 41
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值