[算法笔记]前缀和问题--算法讲解与例题

目录

1. 一维的前缀和算法

生成前缀和数组

计算区间和

2. 二维的前缀和算法

生成前缀和数组

计算区间和

3.题1-寻找数组的中心下标

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


1. 一维的前缀和算法

【模板】前缀和_牛客题霸_牛客网https://www.nowcoder.com/practice/acead2f4c28c401889915da98ecdc6bf?tpId=230&tqId=2021480&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

        该题最简单的办法就是暴力求解,当像要知道从[l, r]范围内的数据和的时候,直接遍历数组从l到r的位置并累加即可。但是这样的话是非常耗时的,如果说该范围始终是整个数组的求和,那么的话时间复杂度就会是O(n * q);相当于N方的时间复杂度了。

        前缀和算法通常是用于快速求出数组中某多个连续区间的和。可以实现算法的计算时间复杂度变为O(1),但是求前缀和算法的时间复杂度是O(N),所以整体的时间复杂度是O(N)了。那么该算法如何操作呢?

生成前缀和数组

        例子:当存在一个数组a,[1, 4, 7, 2, 5, 8, 3, 6, 9],生成一个比他大一个空间大小的dp前缀和数组,数组的元素存放的是数组开头到当前位置的所有数据的和。dp[1]=a[0]+dp[0],这里的dp[0]初始化的时候会被赋值为0,代表的是前0个元素的和,后续就知道有什么用了。dp[2]=a[1]+ap[1]
dp[3]=a[2]+dp[2]。以此类推。dp前缀和数组表示的是目标数组的前i个元素的和。

计算区间和

        那么这样的话,如果想计算[l, r]区间内的数据和的话(这里的l,r是代表的第l个元素,第r个元素),就可以直接使用dp[r] - dp[l - 1]了。因为是[l,r]区间包含l,所以要减去dp[l - 1]位置的和。所以这样的话,就会涉及到[1, r]区间,那么就需要dp[r] - dp[0]了,所以需要提前提供一个dp[0]。当然也可以当左区间为开头的时候,就直接等于dp[r]了,不需要在减去dp[l - 1]也可以。

#include <iostream>
#include <utility>
#include <vector>

int main()
{
    // 输入
    int n = 0, q = 0;
    std::cin >> n >> q;
    // 输入数组元素
    std::vector<long long> nums(n);
    for(int i = 0; i < n; i++)
    {
        std::cin >> nums[i];
    }
    // 输入查询范围
    std::vector<std::pair<int, int>> search_ranges(q);
    for(int i = 0; i < q; i++)
    {
        int l = 0, r = 0;
        std::cin >> l >> r;
        search_ranges[i] = std::make_pair(l, r);
    }

    //计算前缀和
    std::vector<long long> prximp(n + 1);
    //初始化第一个为0,代表的是前0个元素和为0
    prximp[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        prximp[i] = prximp[i - 1] + nums[i - 1];
    }

    //计算结果
    for(int i = 0; i < q; i++)
    {
        //取出范围
        int l = search_ranges[i].first;
        int r = search_ranges[i].second;
        //计算结果
        long long ret = prximp[r] - prximp[l - 1];
        std::cout << ret << std::endl;
    }

    return 0;
}

        

2. 二维的前缀和算法

【模板】二维前缀和_牛客题霸_牛客网https://www.nowcoder.com/practice/99eb8040d116414ea3296467ce81cbbc?tpId=230&tqId=2023819&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

生成前缀和数组

         二维的前缀和顾名思义就是通常用于计算二维数组中某一区间的和。对于一维数组来说,需要多开辟一个空间,然后在dp[0]的位置设置为0,那么二维数组的话,就需要在每行的开头以及没列的开头赋值为0了。所以需要多开一行和多开一列。

原数组

       (B和C的面积是自身加上公共灰色面积)

        计算前缀和:dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + a[i - 1][j - 1] - dp[i - 1][j - 1],就是B的面积和A的面积之和,加上目标数组该位置的元素值,之和减去一个多余的A面积,就是整个的面积,也就是前缀和了。

计算区间和
前缀和数组

         (B和C的面积是自身加上公共灰色面积) 

        计算区间和:dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1],如果想计算区间D的面积的话,就需要整个面积减去B和C的面积,然后再加上一个公共的A区间的面积。

#include <iostream>
#include <vector>

int main() 
{
    // 输入
    int n = 0, m = 0, q = 0;
    std::cin >> n >> m >> q;
    // 输入二维数组
    std::vector<std::vector<int>> nums(n);
    for(int i = 0; i < n; i++)
    {
        nums[i].resize(m);
        for(int j = 0; j < m; j++)
        {
            std::cin >> nums[i][j];
        }
    }

    //构建前缀和数组
    std::vector<std::vector<long long>> dp(n + 1);
    for(int i = 0; i < n + 1; i++)
    {
        dp[i].resize(m + 1);
        for(int j = 0; j < m + 1; j++)
        {
            if(i == 0 || j == 0)
            {
                dp[i][j] = 0;
                continue;
            }
            //计算前缀和
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + nums[i - 1][j - 1] - dp[i - 1][j - 1];
        }
    }

    // 输入查询坐标并计算
    for(int i = 0; i < q; i++)
    {
        //输入坐标
        int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
        std::cin >> x1 >> y1 >> x2 >> y2;
        //计算
        long long ret = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
        std::cout << ret << std::endl;
    }
    return 0;
}

3.题1-寻找数组的中心下标

724. 寻找数组的中心下标 - 力扣(LeetCode)https://leetcode.cn/problems/find-pivot-index/description/

        这个题可以理解为以中间点为分割,左边和右边各是一个区域,那么我们可以使用两个前缀和数组,第一个是左区域的前缀和,第二个数组是有区域的前缀和。因为要有一个点作为中心点,所以说数组其实计算和的元素是n - 1个,所以前缀和数组需要多开一个空间的话,开辟n个就够了,不需要开辟n + 1个空间。

        前缀和数组的赋值和上面的例题是一样的,而后缀和的赋值可以采用逆序的方式,那么前缀和与后缀和数组的下标就分别代表的是左区域元素的和以及右区域元素的和了。如下图案例所示:

        之和,遍历让各个位置充当中心位置,判断前缀和后后缀和是否相等就可以了。

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

class Solution {
public:
    static int pivotIndex(vector<int>& nums) {
        int n = nums.size();
        std::vector<int> ldp(n), rdp(n);

        for(int i = 1; i < n; i++)
            ldp[i] = ldp[i - 1] + nums[i - 1];
        for(int i = n - 2; i >= 0; i--)
            rdp[i] = rdp[i + 1] + nums[i + 1];

        for(int i = 0; i < n; i++)
            if(ldp[i] == rdp[i])
                return i;
        return -1;
    }
};

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

238. 除自身以外数组的乘积 - 力扣(LeetCode)https://leetcode.cn/problems/product-of-array-except-self/description/

        该题和第1题是一样的,需要使用前缀和与后缀和数组一起解决问题,只不过这里的数组是前缀积与后缀积。因为需要计算的是除自身的元素乘积,那么也相当于是少了一个元素,那么再计算前缀和的时候,只需要计算到前n - 1个元素的前缀和即可,后缀和也是一样的,所以还是之开辟n个空间即可。

        前缀和与后缀和数组代表的值分别为,当前下标下左区间的乘积与当前下标下右区间的乘积,所以计算除自身意外的其他元素的乘积,就只需要将当前位置下的前缀和与后缀和相乘就可以得到结果了。

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();

        std::vector ldp(n, 1);
        std::vector rdp(n, 1);

        for(int i = 1; i < n; i++)
            ldp[i] = ldp[i - 1] * nums[i - 1];
        for(int i = n - 2; i >= 0; i--)
            rdp[i] = rdp[i + 1] * nums[i + 1];

        for(int i = 0; i < n; i++)
        {
            ldp[i] = ldp[i] * rdp[i];
        }
        return ldp;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值