【数组】前缀和

前缀和

给出一个数列:

1 2 3 4 5 6 7 8 9 

它的前缀和:

1 3 6 10 15 21 28 36 45

前缀和即:从第一个元素到该元素之和

通常我们会在数组中触及到这类知识。
假设给出原数组 a[5] = {1,2,3,4,5},我们可以得到前缀和数组,假设为S[5] = {1,3,6,10,15}。
那么可以得到: S[n] = S[n-1] + a[n],即求前缀和的公式。

那么前缀和数组有什么用呢?

以下给出一道经典前缀和问题:


(题目出自acwing,侵删)
首先我们先从朴素思想开始考虑,求m次区间和的时间复杂度是O(mn2)

while(m--)
{
  int l , r;
  cin >> l >> r;
  int sum = 0;
  for(int i = l; i <= r; i++)
  {
      sum += num[i];(假设下标从1开始)
  }
  cout << sum << endl;
}

我们会发现,我们没求一次区间和,就要循环一次,那么有没有一种方法可以让O(n)的求和变为O(1)的时间复杂度呢?
答案就是使用前缀和。对区间[l,r],通过对求前缀和过程的分析可以的出,区间和为s[r]-s[l-1]。因此我们可以通过一次运算求出原数组任意一段数据的和。

以下为代码:

点击查看代码
#include<iostream>
#define N 100010
using namespace std;
int nums[N];
int s[N];
int main()
{
    int n,m;
    cin >> n >> m;
    for(int i = 0; i < n; i++)
    {    
        cin >> nums[i+1];
        s[i+1] = s[i] + nums[i+1];//求前缀和
    }
    while(m--)
    {
        int l,r;
        cin >> l >> r;
        cout << s[r]-s[l-1] << endl;//
    }
    return 0;
}


那么我们可以知道,前缀和通常在当我们需要对以区间为单位进行求和求乘积等操作时会用到。

题目

前缀和结合哈希表


https://leetcode.cn/problems/subarray-sum-equals-k/

在数组中:
1.子数组
子数组的定义:一个或连续多个数组中的元素组成一个子数组(子数组最少包含一个元素)

2.子序列
子序列的定义:子序列就是在原来序列中找出一部分组成的序列(子序列不一定连续)

如果使用for嵌套循环暴力枚举O(n2)的方法必然会超时。
那么分析题意,可知求区间和为k的区间个数,即 s[i] - s[j] == k
同样,如果对前缀和数组进行枚举同样是O(n2)的时间复杂度,会超时。
我们用哈希表存储前缀和数组,结合公式s[i] - s[j] == k的变形s[j] = s[i] - k就可以实现O(n)的时间复杂度的解法。
即:对每一个前缀和数组元素s[i],利用哈希表查找s[i] - k是否存在,存在则找到了一个或n个符合题意的区间。
以下为代码

点击查看代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        mp[0] = 1;
        int sum = 0;
        int ans = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            sum += nums[i];
            if(mp.find(sum - k) != mp.end())//
                ans += mp[sum - k];
            mp[sum]++;
        }
        return ans;
    }
};

前缀乘积



https://leetcode.cn/problems/subarray-sum-equals-k/

最先想到的是遍历求乘积,也就是O(n2)的暴力算法,但这不符合题目的要求。
那么我们需要对算法进行优化,从朴素思想出发,遍历求乘积时,从0到n-1累乘,中间跳过i,那么天然地由i分为左右两个区间,根据乘法的结合律,得出answer[i] = i左侧元素的乘积*i右侧元素的乘积。
那么我们只需要提前将每一个i左侧、右侧元素的乘积算出来,就可以实现O(n)时间复杂度的算法。

点击查看代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> answer(n);
        vector<int> l(n);
        vector<int> r(n);
        l[0] = 1;
        r[n - 1] = 1;
        for(int i = 1; i < n; i++)
            l[i] = l[i-1]*nums[i-1];
        for(int i = n - 2; i >= 0; i--)
            r[i] = r[i+1]*nums[i+1];
        for(int i = 0; i < n; i++)
            answer[i] = l[i]*r[i];
        return answer;
    }
};
对于进阶要求,实现额外空间复杂度为O(1)的算法(不包括答案数组),那么只需要在答案数组上进行操作即可。先将i左侧元素的乘积存入答案数组,再将右侧元素的乘积乘进去,就大功告成。
posted @ 2023-01-14 23:52  瞻鹤  阅读(155)  评论(3编辑  收藏  举报