【算法】前缀和:『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、需要快速比较多个区间的和

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

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

相关推荐
luckycoding2 小时前
739. 每日温度
算法·leetcode·职场和发展
一只黑鸟2 小时前
基于STM32的罐装水泥成分实时检测系统设计与实现(含有matlab仿真)
stm32·嵌入式硬件·算法·matlab·毕设
@我漫长的孤独流浪2 小时前
C算法设计与分析------程序设计代码
数据结构·c++·算法
Filotimo_3 小时前
3.5 排序算法
数据结构·算法·排序算法
一个努力编程人3 小时前
机器学习————GBDT算法
人工智能·算法·机器学习
深圳市恒星物联科技有限公司3 小时前
基于图像识别算法与积水传感器的积水监测预警技术方案
人工智能·算法
小美单片机3 小时前
Proteus8.9安装保姆级教程
c语言·c++·算法·51单片机·proteus·大一新生
white-persist3 小时前
【红队渗透】Cobalt Strike(CS)红队详细用法实战手册
java·网络·数据结构·python·算法·安全·web安全
舟舟亢亢3 小时前
算法总结—【动态规划一维、二维、状态压缩】
算法·动态规划