【LeetCode热题100】前缀和

news2024/11/23 10:36:01

这篇博客共记录了8道前缀和算法相关的题目,分别是:【模版】前缀和、【模版】二维前缀和、寻找数组的中心下标、除自身以外数组的乘积、和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和。

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

int main() 
{
    //1. 读取数据
    int n = 0, q = 0;
    cin >> n >> q;
    vector<int> nums(n+1);
    vector<long long> dp(n+1);
    for(int i = 1; i<= n ;i++)
    {
        cin >> nums[i];
        //2. 预处理出来一个前缀和数组
        dp[i] = dp[i-1] + nums[i];
    }

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

   return 0; 
}

题目分析:首先来看暴力解法,题目让我们求l到r的和,我们可以找到l,然后依次往后累加,直到加到r,然后执行q次,所以这种暴力解法的时间复杂度是O(N*q)。这种暴力解法肯定过不了。所以,我们要使用前缀和方法解决这个问题。所谓前缀和,就是快速求出数组中某一个连续区间的和。快速:使用O(1)的时间复杂度找到一次结果。我们从题目中看到,数组元素下标是从1开始的,下标为0的位置我们默认为0。具体来说,第一步:预处理出来一个前缀和数组dp,其中dp[i]表示[1,i]区间内所有元素的和,dp[0]默认设为0,dp[i]=dp[i-1]+arr[i]。第二步:使用前缀和数组。当我们想求[l, r]区间的和时,可以直接用dp[r]-dp[l-1]求解。这样直接求解的时间复杂度为O(1)。

细节问题:为什么我们的下标要从1开始计数?为了处理边界情况。如果下标从0开始,比如,要求下标0-2之间元素之和,按照上面的方法就是求dp[2]-dp[-1],明显越界了,对于这种情况,还需要单独处理。

#include <iostream>
#include <vector>
using namespace std;
 
int main() 
{
    //1.读入数据
    int n = 0, m = 0, q = 0;
    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];
        }
    }
    //2.预处理出一个前缀和矩阵
    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][j-1] + dp[i-1][j] + arr[i][j] - dp[i-1][j-1];
        }
    }
    //3.利用前缀和矩阵
    int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
    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;
    }
}

题目分析:最开始我们想到的肯定是暴力求解,但是很明显暴力求解的时间复杂度很高,是O(n*m*q),所以我们再来说一种更好的解法--前缀和,第一步,我们预处理出来一个前缀和矩阵dp,dp[i][j]表示从[1][1]位置到[i][j]位置这段区间里面所有元素的和。第二步,使用前缀和矩阵。具体计算过程如下:

class Solution {
public:
    int pivotIndex(vector<int>& nums) 
    {
        //1.先创建前缀和数组f,后缀和数组g
        int n = nums.size();
        vector<int> f(n);
        vector<int> g(n);
        for(int i = 1;i<n;i++)
        {
            f[i] = f[i-1] + nums[i-1];
        }
        for(int j = n-2 ; j >= 0 ; j--)
        {
            g[j] = g[j+1] + nums[j+1];
        }
        //2.使用前缀和数组和后缀和数组
        for(int i = 0 ; i < n ; i++)
        {
            if(f[i] == g[i]) return i;
        }
        return -1;

    }
};

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> f(n);
        vector<int> g(n);
        vector<int> ret(n);
        f[0] = 1,g[n-1] = 1;
        //1.先求出前缀积和后缀积数组
        for(int i = 1 ;i < n ; i++ )
        {
            f[i] = f[i-1] * nums[i-1];
        }
        for(int j = n-2 ;j >= 0 ; j-- )
        {
            g[j] = g[j+1] * nums[j+1];
        }
        //2.使用前缀和数组
        for(int i = 0; i < n ; i++)
        {
            ret[i] = f[i] * g[i];
        }

        return ret;
    }
};

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) 
    {
        unordered_map<int,int> hash; // 统计前缀和出现的次数
        int sum = 0, ret = 0;
        hash[0] = 1;
        for(auto c : nums)
        {
            sum += c; //计算当前位置的前缀和
            if(hash.count(sum - k)) ret += hash[sum - k]; //统计个数
            hash[sum]++;
        }
        return ret;
    }
};

题目分析:对于这道题,如果使用暴力求解,其时间复杂度是O(N2),所以我们换一种方法,我们使用前缀和的思想,在以i为结尾的所有子数组中找,假设以i为结尾的子数组前缀和是sum[i],那么就是在[0,i-1]区间中,有多少个前缀和为sum[i]-k。在找有多少个前缀和为sum[i]-k时,我们可以将之前的前缀和放到一个哈希表中,哈希表的key是前缀和,value前缀和的次数。其实我们不需要搞出一个前缀和数组sum[i],而只需有一个变量sum就行,sum是前i个元素的和。

有几个细节需要处理:1.前缀和加入哈希表的时机:在计算i位置之前,哈希表里只保存[0,i-1]位置的前缀和。2.不用真的创建一个前缀和数组,用一个变量sum来标记前一个位置的前缀和即可。3.如果整个前缀和等于k呢,其实我们应该在开始时设置hash[0]=1。

class Solution 
{
public:
    int subarraysDivByK(vector<int>& nums, int k) 
    {
        unordered_map<int, int> hash;
        int sum = 0;
        int reminder = 0;
        int ret = 0;
        hash[0] = 1; // 0这个数的余数
        for(auto e : nums)
        {
            sum += e;  // 当前位置的前缀和
            reminder = (sum % k + k)%k; //修正后的余数
            if(hash.count(reminder)) ret += hash[reminder]; //统计结果
            hash[reminder]++;
        }
        return ret;
    }
};

题目分析:对于这道题,我们先要补充两个知识:1.同余定理,(a-b)/p = k,可以得出a%p=b%p。2.C++/JAVA中,负%正 = 负,修正-->(a%p+p)%p,这样得到的结果就是一个正确的正数。好了,有了这两个补充知识,剩下的就和上道题几乎一样了。

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

    }
};

题目分析:这道题如果直接求会比较困难,我们可以转化一下,将所有的0改为-1,问题就转化为在数组中,找出最长的子数组,使子数组中所有元素的和为0。这样就和之前和为k的子数组的做法很像,也是使用前缀和+哈希表。但是还是有一些区别,1.哈希表中存什么呢?hash<int,int>的第一个int应该是前缀和,第二个int应该是下标。2.什么时候存入哈希表?使用完之后,丢进哈希表。3.如果有重复的<sum,i>,如何存?只保留前面的那一对<sum,i>。4.默认的前缀和为0的情况,如何存?hash[0]=-1。5.长度怎么算?i-j。

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,x2 = min(m-1 , i+k)+1,y2 = min(n-1,j+k)+1;
                ret[i][j] = dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1];
            }
        }
        return ret;
    }
};

题目分析:这道题很明显需要用到二维数组前缀和,首先,我们要预处理得到一个前缀和数组dp,dp元素的求法如下图(先以mat的起始下标为(1,1)为例):

得到前缀和数组之后,假设我们要求(x1,y1)~(x2,y2)区间元素的和,其算法如下图:

按照题目要得到的矩阵,anwser[i][j]所求的就是[i-k][j-k]~[i+k][j+k]区间元素的和,就可以使用上面求ret的方法,但是i-k、j-k、i+k、j+k有可能越界,所以我们不得不考虑这些越界情况:

但是,这道题的mat的其实下标是从0开始的,我们就必须考虑下标的映射关系,dp数组必须要多加一行多加一列,然后dp就可以从[1,1]下标开始,所以,在求dp矩阵时,在找原始矩阵mat时下标要-1。然后在使用dp表求ans矩阵时,要对下标+1,这其实可以在我们计算(x1,y1)~(x2,y2)时,就给下标+1。

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

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

相关文章

【408数据结构】散列 (哈希)知识点集合复习考点题目

苏泽 “弃工从研”的路上很孤独&#xff0c;于是我记下了些许笔记相伴&#xff0c;希望能够帮助到大家 知识点 1. 散列查找 散列查找是一种高效的查找方法&#xff0c;它通过散列函数将关键字映射到数组的一个位置&#xff0c;从而实现快速查找。这种方法的时间复杂度平均为…

自我指导:提升语言模型自我生成指令的能力

人工智能咨询培训老师叶梓 转载标明出处 传统的语言模型&#xff0c;尤其是经过指令微调的大型模型&#xff0c;虽然在零样本&#xff08;zero-shot&#xff09;任务泛化上表现出色&#xff0c;但它们高度依赖于人类编写的指令数据。这些数据往往数量有限、多样性不足&#xf…

配置Java(JDK)环境变量

一、配置JDK环境变量 将JDK-22压缩包加压缩到指定目录下面&#xff0c;本机路径是&#xff1a;C:\Program Files\Java&#xff08;可以加压缩到自己的指定路径&#xff0c;记住这个路径&#xff0c;配置环境变量时候要使用&#xff09;。 鼠标右键“此电脑”&#xff0c;点击“…

独立按键单击检测(延时消抖+定时器扫描)

目录 独立按键简介 按键抖动 模块接线 延时消抖 Key.h Key.c 定时器扫描按键代码 Key.h Key.c main.c 思考 MultiButton按键驱动 独立按键简介 ​ 轻触按键相当于一种电子开关&#xff0c;按下时开关接通&#xff0c;松开时开关断开&#xff0c;实现原理是通过轻触…

Spring框架-----ioc

基本概念 Spring 是一个轻量级的,IOC和AOP的一站式Java 开发框架&#xff0c;是为了简化企业级应用开发而生的 轻量级&#xff1a;框架核心模块体积小 IOC:Inversion of Control&#xff08;控制反转&#xff09;把创建对象的控制权反转给Spring框架管理 以前我们程序中需要…

Linux下构建Docker镜像

Docker在Linux构建镜像 Docker是一种轻量级的容器化技术&#xff0c;可以让开发者将应用程序及其所有依赖项打包到一个独立的容器中&#xff0c;从而实现跨平台和快速部署&#xff0c;在Linux系统上&#xff0c;我们可以使用D0cker来构建自己的镜像&#xff0c;并且可以通过简…

RocketMQ学习(三)

文章目录 1. 高级功能1.1 消息存储1.1.1 存储介质关系型数据库DB文件系统 1.1.2 性能对比1.1.3 消息的存储和发送1&#xff09;消息存储2&#xff09;消息发送 1.1.4 消息存储结构1.1.5 刷盘机制1&#xff09;同步刷盘2&#xff09;异步刷盘3&#xff09;配置 1.2 高可用性机制…

软件设计之JavaWeb(1)

软件设计之JavaWeb(1) 此篇应在MySQL之后进行学习: 路线图推荐&#xff1a; 【Java学习路线-极速版】【Java架构师技术图谱】 尚硅谷全新JavaWeb教程&#xff0c;企业主流javaweb技术栈 资料可以去尚硅谷官网免费领取 此章节最好学完JDBC观看 学习内容&#xff1a; XML概述T…

excel翻译软件有哪些?如何高效提翻译?

你是否曾在面对满屏的英文Excel表格时感到头疼&#xff1f;项目报告、数据分析、财务报表... 当这些重要的信息被语言壁垒阻挡时&#xff0c;效率和理解度都会大打折扣。别担心&#xff0c;只需3分钟&#xff0c;我将带你轻松解锁excel翻译成中文的秘籍。 无论是职场新人还是…

解决浏览器自动将http网址转https

删除浏览器自动使用https的方式 在浏览器地址栏输入&#xff1a;chrome://net-internals/#hsts PS:如果是edge浏览器可输入&#xff1a;edge://net-internals/#hsts 在Delete domain security policies搜索框下&#xff0c;输入要删除的域名,然后点击delete 解决方法&#…

VMware中共享文件夹没了怎么办?

1.进入root su root 需要提前设置密码 sudo passwd root 2.创建一个hgfs文件夹&#xff0c;share就在这里面 sudo mkdir /mnt/hgfs/ 3.输入下面的命令 sudo mount -t fuse.vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other 4.然后就能找到share文件夹了&#xff0c;注意每…

CCF推荐B类会议和期刊总结(计算机网络领域)

CCF推荐B类会议和期刊总结&#xff08;计算机网络领域&#xff09; 在计算机网络领域&#xff0c;中国计算机学会&#xff08;CCF&#xff09;推荐的B类会议和期刊代表了该领域的较高水平。以下是对所有B类会议和期刊的总结&#xff0c;包括全称、出版社、dblp文献网址以及所属…

串口通信协议(UART)

简介 uart通讯协议&#xff0c;是一种成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通信的协议&#xff1b;是一种全双工&#xff0c;设备点对点通信的协议。下面从硬件电路、电平标准和串口参数等方面来了解uart通信协议。 硬件电路 硬件电路非常简单&am…

如何限制与管控员工上网行为?五个管控方法让员工效率倍增!

在现代企业中&#xff0c;互联网是工作中不可或缺的工具&#xff0c;但与此同时&#xff0c;员工在工作时间浏览与工作无关的网站、进行网络娱乐等行为&#xff0c;也成为了影响企业生产力和效率的主要因素之一。如何有效限制和管控员工的上网行为&#xff0c;从而提升工作效率…

利士策分享,逆境破局关键:精准策略

利士策分享&#xff0c;逆境破局关键&#xff1a;精准策略 在人生的征途上&#xff0c;逆境如同试炼场&#xff0c;考验着我们的智慧与勇气。 为了在这片试炼场上稳健前行&#xff0c;我们需要一套具体而精准的应对策略。 以下&#xff0c;是结合实践经验与智慧总结的应对策略…

机器意识的可能性:从计算功能主义到生物自然主义的思考

引言 近年来&#xff0c;人工智能&#xff08;AI&#xff09;的飞速发展引发了关于机器能否具备意识的激烈讨论。这个话题不仅涉及技术层面的挑战&#xff0c;还触及了哲学和神经科学的基本问题。Anil Seth教授作为意识研究领域的权威&#xff0c;他在近期发表的论文中提出了一…

ESP32-驱动ST7789显示

前言 一、TFT_eSPI库的安装 二、TFT_eSPI 的设置 1.文件的路径 2.User_Setup.h的设置 4.连线 三.显示 总结 前言 环境&#xff1a; 芯片&#xff1a;ESP32 软件&#xff1a;Arduino 一、TFT_eSPI库的安装 1.安装TFT_eSPI库,安装时注意安装内容不要错了 如果提示还有…

[数据集][目标检测]血细胞检测数据集VOC+YOLO格式2757张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2757 标注数量(xml文件个数)&#xff1a;2757 标注数量(txt文件个数)&#xff1a;2757 标注…

论文阅读:AutoDIR Automatic All-in-One Image Restoration with Latent Diffusion

论文阅读&#xff1a;AutoDIR: Automatic All-in-One Image Restoration with Latent Diffusion 这是 ECCV 2024 的一篇文章&#xff0c;利用扩散模型实现图像恢复的任务。 Abstract 这篇文章提出了一个创新的 all-in-one 的图像恢复框架&#xff0c;融合了隐扩散技术&#x…