算法沉淀四:前缀和

目录

 1.一维前缀和

2.二维前缀和

724.寻找数组的中心下标

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

560.和为K的子数组

974.和可被K整除的子数组

525.连续数组

1314.矩阵区域和


对于一个给定的数列,它的前缀和数列是这样构成的:前缀和数列的第n项等于原数列前n项的和。例如,有数列a = [1, 2, 3, 4],它的前缀和数列s中,

s[1]=a[1]=1,

s[2]=a[1]+a[2]=1 + 2 = 3,

s[3]=a[1]+a[2]+a[3]=1+2 + 3 = 6,

s[4]=a[1]+a[2]+a[3]+a[4]=1+2+3 + 4 = 10。
主要用途是可以快速地计算数列中某个区间的和。如果想求数列a中从第m项到第n项(m \leq n)的和,利用前缀和可以通过s[n]-s[m - 1](当m > 1时)或者直接用s[n](当m = 1时)来快速得到结果,而不需要每次都从m项开始逐个相加到n项。

还需从题目中发现它的奥秘!

 1.一维前缀和

牛客题链接

描述

给定一个长度为n的数组a1,a2,....ana1​,a2​,....an​.

接下来有q次查询, 每次查询有两个参数l, r.

对于每个询问, 请输出al+al+1+....+aral​+al+1​+....+ar​

输入描述:

第一行包含两个整数n和q.

第二行包含n个整数, 表示a1,a2,....ana1​,a2​,....an​.

接下来q行,每行包含两个整数   l和r.

1≤n,q≤1051≤n,q≤105
−109≤a[i]≤109−109≤a[i]≤109
1≤l≤r≤n1≤l≤r≤n

输出描述:

输出q行,每行代表一次查询的结果.

示例1

输入:

3 2
1 2 4
1 2
2 3

输出:

3
6

题目简单来说就是输出数组下标 l 到 r 之间的总和。

首先创建一个dp数组,用来存每个位置的前缀和,最后利用相减,就可以实现了。

当然要注意第一个元素,下标为0,也可以在-1的位置增加一个虚拟节点。

#include<iostream>
using namespace std;

const int N = 100010;
long long arr[N], dp[N];
int n, q;

int main()
{
	cin >> n >> q;
	//读取数据
	for (int i = 1; i <= n; i++) cin >> arr[i];
	//处理前缀和数据
	for (int i = 1; i <= n; i++) dp[i] = dp[i - 1] + arr[i];

	while (q--)
	{
		int l, r;
		cin >> l >> r;
		//计算区间和
		cout << dp[r] - dp[l - 1] << endl;
	}
	return 0;
}

2.二维前缀和

牛客题链接

描述

给你一个 n 行 m 列的矩阵 A ,下标从1开始。

接下来有 q 次查询,每次查询输入 4 个参数 x1 , y1 , x2 , y2

请输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和,

输入描述:

第一行包含三个整数n,m,q.

接下来n行,每行m个整数,代表矩阵的元素

接下来q行,每行4个整数x1, y1, x2, y2,分别代表这次查询的参数

1≤n,m≤10001≤n,m≤1000
1≤q≤1051≤q≤105
−109≤a[i][j]≤109−109≤a[i][j]≤109
1≤x1≤x2≤n1≤x1​≤x2​≤n
1≤y1≤y2≤m1≤y1​≤y2​≤m

输出描述:

输出q行,每行表示查询结果。

示例1

输入:

3 4 3
1 2 3 4
3 2 1 0
1 5 7 8
1 1 2 2
1 1 3 3
1 2 3 4

输出:

8
25
32

题目的解析 :(x1,y1),(x2,y2)相当于一个矩形,求这个矩形内的所有元素的总和。

主要理解如何处理前缀和矩阵:
dp[i][j] =arr[i][j] + dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1];

使用:

dp[x2][y2] - dp[x2][y1-1] -dp[x1-1][y2] +dp[x1-1][y1-1];

 

 

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


int main()
{
    //1、读入数据
    int n = 0, m = 0, q = 0;
    cin >> n >> m >> q;

    //注意是 n+1 和 m+1,从小标1开始,如果是n,那么只有1~n-1,n+1才有1~n
    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、预处理前缀和dp矩阵
    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] =arr[i][j] + dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1];

    //3、使用前缀和矩阵
    int x1,x2,y1,y2;
    while(q--)
    {
        cin>>x1>>y1>>x2>>y2;

        cout<<dp[x2][y2] - dp[x2][y1-1] -dp[x1-1][y2] +dp[x1-1][y1-1]<<endl;
    }
    return 0;
}

724.寻找数组的中心下标

给你一个整数数组 nums ,请计算数组的 中心下标 

数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。

如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。

如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

示例 1:

输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

示例 2:

输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。

示例 3:

输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。

创建两个dp表,一个往后走,一个往前走,如果i位置,f(i) = g(i),这个则存在。 

//class Solution {
//public:
//    int pivotIndex(vector<int>& nums) {
//        
//        int sumLeft = 0,sumRight = accumulate(nums.begin(),nums.end(),0);
//        for (int i = 0; i < nums.size(); ++i)
//        {
//            sumRight -= nums[i];
//            if (sumLeft == sumRight)
//                return i;
//            sumLeft += nums[i];
//        }
//        return -1;
//        
//    }
//};


/*
f[i] : [0,i-1]的和
g[i] : [i+1, n-1]的和
细节问题
f[0] = 0,g[n-1] = 0
*/

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

        int n = nums.size();
        vector<int> f(n), g(n);
        //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++)
            if (f[i] == g[i])
                return i;

        return -1;
    }
};

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

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内。

请 不要使用除法,且在 O(n) 时间复杂度内完成此题。

示例 1:输入: nums = [1,2,3,4] 输出: [24,12,8,6]

示例 2:输入: nums = [-1,1,0,-3,3] 输出: [0,0,9,0,0]

也是创建两个dp表,一个往后累乘,一个往前累乘 。

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


//class Solution {
//public:
//    vector<int> productExceptSelf(vector<int>& nums) {
//        int len = nums.size();
//        if (len == 0)
//        {
//            return {};
//        }
//        vector<int> ans(len, 1);
//        ans[0] = 1;
//        int tmp = 1;
//        for (int i = 1; i < len; ++i)
//        {
//            ans[i] = ans[i - 1] * nums[i - 1];
//        }
//        for (int i = len - 2; i >= 0; --i)
//        {
//            tmp *= nums[i + 1];
//            ans[i] *= tmp;
//        }
//        return ans;
//    }
//};


//前缀和

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        //用空间替换时间
        //f表示前缀积,g表示后缀积

        int n = nums.size();
        vector<int> f(n), g(n);
        //1.预处理前缀积数组以及后缀积数组
        f[0] = 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];

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

        return ret;

    }
};

560.和为K的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 

子数组是数组中元素的连续非空序列。

示例 1:

输入:nums = [1,1,1], k = 2
输出:2

示例 2:

输入:nums = [1,2,3], k = 3
输出:2

#include<iostream>
#include<vector>
#include<unordered_map>

using namespace std;
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        hash[0] = 1;

        int sum = 0, ret = 0;
        for (auto x : nums)
        {
            sum += x;//计算当前位置的前缀和
            if (hash[sum - k])
                ret += hash[sum - k];//为什么是+= 因为后期如果是有相同的前缀和,就不只一个数组相加为k
            hash[sum]++;
        }
        return ret;
    }
};

974.和可被K整除的子数组

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的非空 子数组 的数目。

子数组 是数组中 连续 的部分。

示例 1:

输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

示例 2:

输入: nums = [5], k = 9
输出: 0
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        hash[0 % k] = 1;//0 这个数的余数

        int sum = 0, ret = 0;
        for (auto e : nums)
        {
            sum += e;//算出当前位置的前缀和
            int r = (sum % k + k) % k;//防止负数余数,修正后的余数
            if (hash.count(r))
                ret += hash[r];//统计次数
            hash[r]++;
        }
        return ret;
    }
};

525.连续数组

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:

输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

示例 2:

输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

 

这个题普遍思路是,在一个长度为10的数组中,有5个是0,5个是1,既然这样,则可以说含有1的个数是长度的一半,比如n个连续,则这个数组累加的话,最后的结果会是n/2。

但这里用前缀和的思想来实现,在累加之前把0变成-1,这样只要累加的结果为0,则能说明存在,累加之后就丢进哈希表。并且这个哈希表的second存的是数组下标,因为要返回长度。

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

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        unordered_map<int, int> hash;//前缀和和下标
        hash[0] = -1;//默认有一个前缀和为0的情况

        int ret = 0, sum = 0;
        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;
    }
};

1314.矩阵区域和

给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和:

i - k <= r <= i + k,

j - k <= c <= j + k 且

(r, c) 在矩阵内。

示例 1:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[12,21,16],[27,45,33],[24,39,28]]

示例 2:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2
输出:[[45,45,45],[45,45,45],[45,45,45]]
class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        int m = mat.size(), n = mat[0].size();
        //1.预处理一个前缀和矩阵
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + mat[i-1][j-1];
        //2.使用
        vector<vector<int>> ret(m, vector<int>(n));
        for (int i = 0; i < m;i++)
            for (int j = 0; j < n; j++)
            {
                int x1 = max(0,i-k) + 1,y1 = max(0,j-k) + 1;
                int x2 = min(m-1,i+k) +1,y2 = min(n-1,j+k) + 1;
                ret[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1-1] + dp[x1 - 1][y1 - 1];
            }
        return ret;
        
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值