算法专题四: 前缀和

news2024/11/25 21:46:55

目录

  • 1. 前缀和
  • 2. 二维前缀和
  • 3. 寻找数组的中心下标
  • 4. 除自身以外数组的乘积
  • 5. 和为k的子数组
  • 6. 和可被K整除的子数组
  • 7. 连续数组
  • 8. 矩阵区域和

博客主页:酷酷学!!!
感谢关注~


1. 前缀和

在这里插入图片描述

算法思路:

根据题意, 创建一个前缀和数组, dp[i] = dp[i -1] + arr[i], 再使用前缀和数组, 要求的区域ret = dp[r] - dp[l-1], 这里我们为什么要这样求dp[i]呢? 还要绕一大圈子, 直接相加不就行了 , 但是如果直接相加求还不如我们的暴力解法呢, 这里还要开辟空间, 但是我们使用dp[i]求解只需遍历一遍数组即可求出前缀和

需要注意:

  1. 创建前缀和数组默认从0开始, 如果我们从1开始访问则需要先将数组初始化好,
  2. 因为dp[i] 的大小可能超过int所以需要创建long long类型的数组.

编写代码:

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

int main() 
{
    int n = 0, q = 0;
    cin>>n>>q;
    vector<long long> dp(n+1,0);
    int num = 0;
    for(int i = 1; i <= n; i++)
    {
        cin>>num;
        dp[i] = dp[i-1] + num;
    }
    while(q--)
    {
        int l,r;
        cin>>l>>r;
        cout<<dp[r] - dp[l-1]<<endl;
    }
    return 0;
}
// 64 位输出请用 printf("%lld")

2. 二维前缀和

在这里插入图片描述
算法思路:

在这里插入图片描述

在这里插入图片描述

分析题意, 很显然如果我们使用暴力解法一定会超时的.
首先分析题目, 我们可以先预处理出来一个前缀和矩阵如下图所示, 求出每一个dp[i][j]
在这里插入图片描述
然后我们使用dp[i][j],根据所求的区域我们就可以找出一个求出结果的公式, 于是我们就可以搞出来一个时间复杂度为O(N)的算法,当然空间复杂度也为O(N).

在这里插入图片描述
编写代码:

#include<iostream>
#include<vector>
using namespace std;
int main()
{   
    int n,m,q;
    cin>>n>>m>>q;
    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];
        }
    }

    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] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + arr[i][j];
        }
    }
    int x1,y1,x2,y2;
    while(q--)
    {
        cin>>x1>>y1>>x2>>y2;
        cout<<dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]<<endl;
    }
    return 0;
}

3. 寻找数组的中心下标

在这里插入图片描述

算法思路1:

分析题意, 发现可以使用前缀和来解决, 首先预处理出来一个前缀和数组, 然后我们只需在遍历一遍前缀和数组, 只要找到一个位置前面的区域和后面的区域相同, 则就找到了该位置, 但是注意我们的dp是从1开始的所以返回结果需要-1, 如果没有找到返回-1即可, 注意预处理前缀和数组的时候与原数组的映射关系.

编写代码:

class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n+1,0);
        for(int i = 1; i <= n ;i++)
        {
            dp[i] = dp[i-1] + nums[i-1];
        }
        for(int i = 1; i <= n ;i++)
        {
            if(dp[i-1] == (dp[n] - dp[i]))
                return i-1;
        }
        return -1;
    }
};

算法思路二:

既然我要算左边和右边的和是否相等, 那么我们不妨弄两个的dp数组, 一个前缀和一个后缀和, 对于前缀和我们只需要求出i之前的所有元素之和即可, 对于后缀和我们只需要求出i位置之后的所有元素之和即可, 但是注意细节我们需要考虑第一次计算会访问越界的情况, 所以我们需要提前把f[0]和g[n-1]这个位置处理好,而vector默认就为0.
在这里插入图片描述

在这里插入图片描述

编写代码:

class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n),g(n);
        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(g[i] == f[i]) return i;
        }
        return -1;
    }
};

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

在这里插入图片描述
算法思路:

有了上一题思路二的方法, 对于这道题我们不难解决, 只需求出对于i之前的前缀积以及对于i之后的后缀积即可, 遍历i位置此时answer[i]的位置就是f[i]*g[i], 但是注意细节,对于f[0]和g[n-1]我们要处理成1, 默认第一个位置之前的积为1,最后一个位置也是.

在这里插入图片描述

编写代码:

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n),g(n),ret(n);
        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];
        for(int i = 0; i < n;i++)
            ret[i] = g[i] * f[i];
        return ret;
    }
};

5. 和为k的子数组

在这里插入图片描述

算法思路:

首先我们肯定会想到暴力求解, 但是时间复杂度为O(N^2), 那可不可以使用双指针呢, 也就是我们的滑动窗口, 也不可以, 因为会有负数, 并没有单调性.

在这里插入图片描述

对于一个数组, 我们要求这个数组中和为k的子数组, 比如遍历到i位置, 我们要求i往前的和为k的子数组, 仅需从0位置查找, 找到和为sum[i] - k的子数组即可, 所以我们的前缀和思想又可以派上用场了.

首先我们先计算出前缀和, 但是我们并不需要创建一个前缀和数组, 仅需把每一次的结果记录到sum即可, 遍历一遍数组, 遍历到i位置就找i位置之前有没有sum = nums[i] - k的, 我们可以把每一次的结果放到哈希表中, 但是注意我们并不需要i这个位置的和, 而是i之前的, 所以我们只保存i之前的前缀和即可, 如果有则ret++,继续下一次查找, 但是注意如果整个数组等于k,也就是遍历到第一个位置我们需要在0到-1这个区间查找, 所以我们需要处理hash[0] = 1.

在这里插入图片描述

编写代码:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int> hash;
        int sum = 0, ret = 0;
        hash[0] = 1;
        for(int i = 0; i < nums.size();i++)
        {
            sum += nums[i];
            if(hash.count(sum - k)) ret += hash[sum - k]; 
            hash[sum]++;
        }
        return ret;
    }
};

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

在这里插入图片描述

算法思路:

本道题依然不能使用滑动窗口来解决, 更上一道题思路类似, 只不过我们这道题哈希表里面存放的是sum的余数, 当遍历到i位置的时候只需要判断在i位置之前和除以k的余数是否等于sum除以k的余数, 因为同余定理, 可以看下图

在这里插入图片描述
也就是我们仅需在i位置前找到x%k等于sum%即可, 所以判断的时候我们需要判断sum的余数, 所以哈希表里面我们需要存放余数, 但是C++中余数是由负数的, 负数和正数取余结果不一样, 所以我们需要进行修正. 使用sum遍历数组, 并且求出sum的余数r, 然后先判断sum之前的余数是否等于r, 如果等于则更新ret,最后将这个r也丢入到哈希表中.

在这里插入图片描述

在这里插入图片描述

编写代码:

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int,int> hash;
        hash[0 % k] = 1;
        int sum = 0, ret = 0;
        for(int i = 0; i < nums.size() ; i++)
        {
            sum += nums[i];
            int r = (sum % k + k) % k;
            if(hash.count(r)) ret += hash[r];
            hash[r]++;
        }
        return ret;
    }
};

7. 连续数组

在这里插入图片描述

算法原理:

这一道题没有单调性, 我们还是不能使用滑动窗口, 先把0修改成-1,我们就可以发现规律, i位置和在[0,i-1]位置的值相等, 即就能找到k数组, 因为正负抵消, 遍历到i位置我们判断该位置之前是否有和正好等于sum, 如果有则我们更新结果, 这里注意我们哈希表中需要存储下标, 因为结果需要我们求出长度, 如果有重复的sum, 我们只保存最早出现的那个下标.
在这里插入图片描述

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        unordered_map<int,int> hash;
        hash[0] = -1;
        int sum = 0 ,ret = 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;
    }
};

8. 矩阵区域和

在这里插入图片描述

算法思路:

首先读懂题意, 如下图所示, 也就是answer数组中的每一个位置就是mat当前位置的值向周围拓展k个单位元素的和.接下来进行分析

在这里插入图片描述

这道题要求我们求出拓展k个单位的和, 我们应该联想到一个算法, 二维前缀和dp.回忆一下二维前缀和的用法.如果说我们要求一个矩阵的前缀和数组,
则dp[ i ] [ j ] = dp[ i - 1 ] [ j ] + dp[ i ] [ j-1 ] - dp[ i - 1] [j - 1] + arr[ i ] [ j ]

在这里插入图片描述
对于使用则有, 如果我们要求某一段区域的和,
ret = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]

在这里插入图片描述
可以看出, 我们只要想求某段区域的和则前缀和数组中, 仅需知道这块区域的左上角下标个右下角下标即可,本题我们也可以用这样的思路, 但是有几个细节需要注意:

1, 注意我们创建dp数组时, 为了避免越界访问一般下标从1开始, 但是本题所给的数组mat是从下标0 开始, 所以我们它的dp数组需要稍作修改, 当时用到mat时, 将下标+1处理

在这里插入图片描述

然后创建我们的ret数组, ret数组中的每一个位置我们都需要使用到dp数组, 所以需要求每一个位置的左上角位置与右下角位置的下标, 以便于正确的使用dp数组.根据下面我们求出该区域与使用dp数组的映射关系, 然后对于每一个位置分别求其结果.

在这里插入图片描述
编写代码:

class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        int m = mat.size(), n = mat[0].size();
        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];
            }
        }
        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;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2195118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go编译为可执行文件

在window下打包成其他系统可运行的文件 1.在window下打包成window下可执行文件 在项目main.go同级目录下&#xff0c;逐条执行以下命令 set CGO_ENABLED0 set GOOSwindows set GOARCHamd64 go build -o main-windows.exe main.go 2.在window下打包成linux 在项目main.go同级目…

Android Codec2 CCodec(十六)C2AllocatorGralloc

这一篇文章我们一起来瞧瞧2D&#xff08;Graphic&#xff09; buffer分配器C2AllocatorGralloc是如何工作的。 1、GraphicBuffer 在Android系统中&#xff0c;GraphicBufferAllocator和GraphicBufferMapper是与图形缓冲区&#xff08;Graphic Buffers&#xff09;管理相关的重…

Python爬取b站视频:验证cookie是否有效

具体代码 import requestsheaders {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0,Referer: https://www.bilibili.com/,Origin: https://www.bilibili.com } def readCooki…

Nginx02-安装

零、文章目录 Nginx02-安装 1、Nginx官网 Nginx官网地址&#xff1a;http://nginx.org/ 2、Nginx下载 &#xff08;1&#xff09;Nginx下载 下载页地址&#xff1a;http://nginx.org/en/download.html &#xff08;2&#xff09;更老版本下载 下载页地址&#xff1a;http…

四、链表————相关算法探讨(持续更新中)

链表中相关算法探讨 前言一、移除链表元素1.1 思路分析1.2 解法探讨1.2.1 直接删除1.2.2 创建虚拟头节点来删除1.2.3 递归版删除 二、反转列表2.1 思路分析2.2 做法2.2.1 创建新链表方式2.2.2 双指针法2.2.3 递归法 三、两两交换链表中的节点3.1 思路分析3.2 解法探讨3.2.1 不使…

重磅 | 清华大学刘知远老师领衔的大模型公开课2024年第二季来了!助教阵容强大,零基础大模型从入门到精通,看这个就够了!

本文由readlecture.cn转录总结。ReadLecture专注于音、视频转录与总结&#xff0c;2小时视频&#xff0c;5分钟阅读&#xff0c;加速内容学习与传播。 更多讲座、采访干货内容&#xff0c;欢迎关注公众号“ReadLecture”获取&#xff01;公众号后台直接回复&#xff0c;可与公众…

RK3568笔记六十四:SG90驱动测试

若该文为原创文章,转载请注明原文出处。 前面有测试过PWM驱动,现在使用两种方式来产生PWM驱动SG90,实现舵机旋转任意角度 方法一:使用硬件PWM 方法二:使用高精度定时器,GPIO模拟PWM. 一、PWM子系统框架 二、SG90控制方法 舵机的控制需要MCU产生一个周期为20ms的脉冲信号…

python实现DES算法

DES算法 一、算法介绍1.1 背景1.2 原理1.3 基本功能函数1.3.1 初始置换函数 I P IP IP1.3.2 f f f 轮函数1.3.3 逆初始置换函数 I P − 1 IP^{-1} IP−1 1.4 子密钥的生成 二、代码实现2.1 子密钥生成实现2.2 DES加解密实现2.3 完整代码 三、演示效果 一、算法介绍 1.1 背景…

SpringBoot框架在旅游管理中的应用与实践

第三章 系统分析 3.1可行性分析 对所有的系统来说&#xff0c;都有可能会受到时间和空间上的制约。所以&#xff0c;我们在设计每一个项目的时候&#xff0c;必须对该系统实行可行性分析&#xff0c;这样不但能够降低项目的危害&#xff0c;还能改降低人力、物力和财力的损耗。…

C++(十七) 多态

一、 多态概念 多态&#xff08;polymorphism&#xff09;&#xff0c;通俗来说&#xff0c;就是多种形态。多态分为编译时多态&#xff08;静态多态&#xff09;和运行时多态&#xff08;动态多态&#xff09;。这里我们重点讲运行时多态&#xff0c;同时简单介绍编译时多态。…

swagger2.9.2 和 springboot3.3.4版本冲突问腿

swagger2.9.2 和 springboot3.3.4版本冲突问腿 问题描述&#xff1a;当我们使用 swagger 2.9.2版本的时候&#xff0c;如果恰好我们使用的 springboot 版本是3.x版本&#xff0c;会出现启动报错的问题 解决办法&#xff1a;直接使用swagger 3.x 版本和 springboot 3.x 版本 …

window 安装永洪BI Desktop版本教程

本教程基于永洪BI Desktop 10.2 一、下载软件包 &#xff08;下载需要注册&#xff0c;以便接收License邮件激活码&#xff09;&#xff0c;地址如下&#xff1a;桌面智能数据分析工具_vividime Desktop数据分析软件-永洪科技vividime Desktop是一款轻量级桌面智能数据分析工具…

【探测器】线阵相机中的 TDI 技术

【探测器】线阵相机中的 TDI 技术 1.背景2.TDI相机3.场景应用 1.背景 TDI 即Time Delay Integration时间延迟积分。 TDI相机是线阵相机的一种特殊类型&#xff0c;带有独特的时间延迟积分&#xff08;TDI&#xff09;技术。 换句话说&#xff0c;TDI相机是线阵相机的一个高级版…

HCIP-HarmonyOS Application Developer 习题(七)

&#xff08;判断&#xff09;1、HarmonyOs跨端迁移和多端协同&#xff0c;是使用不同的FA/PA&#xff0c;在不同设备间运行来实现完整的业务。 答案&#xff1a;错误 分析&#xff1a; &#xff08;判断&#xff09;2、HarmonyOs的方舟开发框架包含基于TS扩展的类Web开发范式…

【RPC】—Thrift协议 VS Protobuf

Thrift协议 & VS Protobuf ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记仓库&#x1f449;https://github.com/A-BigTree/tree-learning-notes 个人主页&#x1f449;https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 文章目录 Thrift协议 & VS Pro…

云原生(四十九) | WordPress源码部署

文章目录 WordPress源码部署 一、WordPress部署步骤 二、创建项目目录 三、上传源码到WordPress 四、配置安全组 五、配置WordPress 六、访问WordPress WordPress源码部署 一、WordPress部署步骤 第一步&#xff1a;创建项目目录 第二步&#xff1a;上传源码到项目目…

ARM(5)内存管理单元MMU

一、虚拟地址和物理地址 首先&#xff0c;计算机系统的内存被组成一个由M个连续的字节大小组成的数组。每字节都会有一个唯一的物理地址。CPU访问内存最简单的方式就是使用物理地址。如下图&#xff1a; 图 1 物理地址,物理寻址 而现在都是采用的都是虚拟寻址的方法。CPU生成一…

51单片机的自动制冷系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温度传感器继电器LED、按键和蜂鸣器等模块构成。适用于车载便携式自动制冷系统、冰箱制冷、温度控制等相似项目。 可实现功能: 1、LCD1602实时显示当前温度 2、温度传感器DS18B20采集温度 3、按键可设置温度的阈…

【开发心得】筑梦上海:项目风云录(6)

目录 会海跳槽 票务开启 漂泊在外的日子 未完待续 会海跳槽 随着时刻表的出炉&#xff0c;意味着大规模的界面开发逐步进入正规。项目组里陆陆续续引进了8个人&#xff0c;最多的时候&#xff0c;同时有10个人在现场。“松工”为我们准备的办公室坐的满满当当&#xff0c;…

攸信动态丨厦门火炬大学堂携手厦门攸信技术,共探盈趣汽车电子数字化转型标杆之路

今日上午&#xff0c;在厦门市工信局指导下&#xff0c;由厦门盈趣汽车电子有限公司、厦门攸信信息技术有限公司携手北京赛昇科技有限公司与厦门火炬大学堂联合举办的“厦门中小企业数字化转型人才培训&#xff08;第14期&#xff09;”活动&#xff0c;在热烈而充实的氛围中圆…