【算法】前缀和:『560. 和为 K 的子数组 & 1314.矩阵区域和』

🎬 个人主页MSTcheng · CSDN
🌱 代码仓库MSTcheng · Gitee
🔥 精选专栏 : 《C语言
数据结构
《算法学习》
C++由浅入深

💬座右铭: 路虽远行则将至,事虽难做则必成!


前言:上一篇文章中我们向大家介绍了,二分算法,本篇我们就来介绍一下前缀和算法。

我们通过一道例题来讲解前缀和:

一、【模板】前缀和

1.1题目解析

1.2算法原理

1、暴力算法

上面有一个数组,如果我们要计算某一个区间的值我们直接去暴力遍历,那么我可以计算时间复杂度为O(N)的,然后这只是一次计算,题目中还要进行m次查询(给出多个区间),所以时间复杂度是O(n*m)

下面来看看基于暴力算法的优化:

2、使用前缀和

细节问题:为什么要从1开始计数?

  1. 为了处理边界情况,如果我们从0开始,那么要计算(0,2)区间的数不就是,dp[2]-dp[-1]了?而-1是我们未定义的访问必然崩溃!所以dp数组下标从1开始就是为了避免这样的情况发生。
  2. 所以我们在设计dp数组的时候可以给dp[0]初始化一下,将dp[0]设为0,当然不同题目的要求不同,不是所有情况都将dp[0]设为0!

3、代码编写

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() 
{
    //输入数据
    int n=0,m=0;
    cin>>n>>m;
    vector<int> arr(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>arr[i];
    }

    //预处理出来一个前缀和数组
    vector<long long> dp(n+1); //使用long long防止溢出
    for(int i=1;i<=n;i++)
    {
        dp[i]=dp[i-1]+arr[i];
    }

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

二、【模板】二维前缀和

2.1题目解析

2.2算法原理

注意:
1、在二维dp数组我们在与原数组arr同等大小的规模上,在最上面和最坐标添加上一行和一列,目的就是为了防止像一维dp数组那样出现未定义的情况比如dp[-1][-1]

2、同时我们要注意原arr数组与dp数组下面的映射关系:

  1. dp 表到 arr 矩阵,横纵坐标减⼀;
  2. arr 矩阵到 dp 表,横纵坐标加⼀。

2.3代码编写

cpp 复制代码
#include <iostream>
#include<vector>
using namespace std;

int main() 
{
    //输入数据
    int n=0,m=0,q=0;
    cin>>n>>m>>q;
    //1、注意二维数组的初始化方式 题目要求arr数组行和列都从下标1开始
    //2、使用long long 防止溢出
    vector<vector<long long>> arr(n+1,vector<long long>(m+1,0));
  

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>arr[i][j];
        }
    }

    //预处理出来一个前缀和数组 所以前缀和数组可以和arr数组一样大
    vector<vector<long long>> dp(n+1,vector<long long>(m+1,0));
    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];
        }
    }

    //使用前缀和数组 q次访问
    while(q--)
    {
        int x1=0,y1=0,x2=0,y2=0;
        cin>>x1>>y1>>x2>>y2;

        long long ret=dp[x2][y2]-dp[x2][y1-1]-dp[x1-1][y2]+dp[x1-1][y1-1];
        cout<<ret<<endl;
        
    }


}

注意:

但是本题的arr数组下标题目要求是从1开始的!所以与dp数组下标直接对应,我们求dp[i][j]就是求arr[1][1]到arr[i][j]所有元素的值!而不是求arr[0][0]到arr[i-1][j-1]的值! 所以本题的dp数组可以跟原数组一样大!

三、560. 和为 K 的子数组

3.1题目解析

3.2算法原理

3.3代码编写

cpp 复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
       unordered_map<int,int> hash;
       //默认第一个前缀和为1 当给出的数组已经等于k时就是一种情况 所以哈希表0的位置默认给成1
       hash[0]=1;

       int sum=0,ret=0; //sum计算前缀和 ret统计最终结果
       for(auto& x:nums)
       {
            sum+=x;//sum加等x后就变成了下一次的前缀和 
            if(hash.count(sum-k))
            {
                //此时判断一下 哈希表里有没有满足sum-k 的前缀和存在 有的话ret就加等于满足sum-k 所有前缀和的次数
                ret+=hash[sum-k];
            }
            hash[sum]++;//不断将前缀和放入哈希表
       }
       return ret;
   }
};

4、1314. 矩阵区域和

4.1题目解析

4.2算法原理

4.3编写代码

cpp 复制代码
class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
          //搞出一个dp数组
        int m=mat.size();//m是行大小!
        int n=mat[0].size();//n是列大小!


        //二维数组就相当于拿n个一维(vector)来初始化
        //注意dp数组要多一行多一列 因为dp数组默认从1开始
        //而dp[1]存的是mat[0]周围k个元素的和
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                                        //这里要尤为注意mat[i-1][j-1]
                dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mat[i-1][j-1];

            }
        }

        //使用dp数组
        //创建一个ans数组
        //int x1=0,y1=0,x2=0,y2=0;
        vector<vector<int>> ans(m,vector<int>(n));
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                //这里的x1,x2,y1,y2都加一是因为加一才能与
                //dp数组的下标对应
                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;
                ans[i][j]=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1];
            }
        }
    return ans;

    }
};

五、总结

一、最典型适用场景(必用前缀和)
1、多次查询区间和

给一个数组,问很多次:[l, r] 的和是多少→ 前缀和 O (1) 回答,不用每次遍历。

2、求有多少个子数组和 = k

最经典:nums 中有多少个子数组和为 target→ 前缀和 + 哈希表 O (n)。

3、求子数组和 ≥ k / ≤ k 的最短 / 最长长度

正数组时:前缀和 + 双指针 / 二分。

4、环形数组、连续子数组和问题

拆成两段和,用前缀和快速算。

5、二维矩阵里求子矩阵和

求矩形区域和 → 二维前缀和。

6、需要快速比较多个区间的和

比如判断哪段和最大 / 最小、是否相等。

看到区间和、子数组和、多次查询和 → 前缀和
会改数组 → 不用前缀和

相关推荐
汀、人工智能13 小时前
[特殊字符] 第103课:单词搜索II
数据结构·算法·均值算法·前缀树·trie·单词搜索ii
wanderist.13 小时前
算法模板-字符串
数据结构·算法·哈希算法
xiaoye-duck13 小时前
《算法题讲解指南:动态规划算法--子序列问题》--29.最长递增子序列的个数,30.最长数对链,31.最长定差子序列
c++·算法·动态规划
Yzzz-F13 小时前
Problem - 2180D - Codeforces
算法
moonsea020313 小时前
2023.9.25
算法
汀、人工智能13 小时前
[特殊字符] Python基础语法速成教程
算法·链表·均值算法·哈希表·lru缓存·python基础语法速成教程
tankeven13 小时前
HJ164 太阳系DISCO
c++·算法
来自远方的老作者14 小时前
第7章 运算符-7.1 算术运算符
开发语言·数据结构·python·算法·算术运算符
white-persist15 小时前
【vulhub weblogic CVE-2017-10271漏洞复现】vulhub weblogic CVE-2017-10271漏洞复现详细解析
java·运维·服务器·网络·数据库·算法·安全
汀、人工智能15 小时前
[特殊字符] 第9课:三数之和
数据结构·算法·数据库架构·图论·bfs·三数之和