【基础算法总结】前缀和

news2024/9/20 0:53:02

目录

  • 一,前缀和算法介绍
  • 二,算法原理和代码实现
    • 【模板】前缀和
    • 【模板】二维前缀和
    • 724.寻找数组的中心下标
    • 238.除自身以外数组的乘积
    • 560.和为k的子数组
    • 974.和可被k整除的子数组
    • 525.连续数组
    • 1314.矩阵区域和
  • 三,算法总结

一,前缀和算法介绍

前缀和也是基础算法之一,它一般应用于快速求出某个连续区间的和/积。前缀和一般包括一维前缀和,二维前缀和,其实它的做题流程有一点点像动态规划的感觉前缀和算法的时间复杂度是O(1)

前缀和与二分查找类似,也是有固定模板的,下面的前两题分别就是一维/二维的前缀和模板题

二,算法原理和代码实现

【模板】前缀和

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

解法1:暴力解法,O(n*q)。就是根据它给的区间,一个一个数加起来,绝对超时

解法2:前缀和,O(q)+O(n)
使用前缀和算法的固定流程
(1) 预处理出来一个前缀和数组
创建一个dp数组,与原数组同规模大小
dp中每个元素 dp[i] 表示:arr数组中 [1,i ] 区间所有元素的和
可得递推公式:dp[i] = dp[i - 1] + arr[i]
在这里插入图片描述
(2) 使用前缀和数组
就是处理每次查询,此时要输入两个数 left 和 right 表示查询区间
通过分析可以发现:arr 数组里 [left, right] 的区间数据和 = dp[right] - dp[left - 1]
在这里插入图片描述

细节问题:

为什么下标要从1开始
如果下标从0开始,会出现边界问题 ,根据上面的公式分析
在这里插入图片描述
下标从1开始就是为了处理边界情况

代码实现:

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

int main() 
{
   // 读入数据
   int n, q;
   cin >> n >> q;
   vector<int> arr(n+1);
   for(int i = 1; i <=n; i++)
        cin >> arr[i];

    // 预处理前缀和数组
    vector<long long> dp(n+1); // 防溢出
    for(int i = 1; i <= n; i++)
        dp[i] = dp[i-1] + arr[i];
    
    // 使用前缀和数组
    while(q--)
    {
        int left, right;
        cin >> left >> right;
        cout << dp[right] - dp[left-1] << endl;
    }
}

【模板】二维前缀和

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

解法1:暴力解法,O(n * m * q)。就是根据给的两个坐标,一个一个数加,绝对超时

解法2:前缀和,O(m * n)+O(q)
下面的内容将详细介绍前缀和矩阵的处理和使用:

(1) 预处理出一个前缀和矩阵
创建一个前缀和数组dp,大小规模与arr相同
dp[i][j] 表示:从[1][1] 位置到 [i][j] 位置,这段区间里面所有元素和
以arr矩阵中的 [i][j] 位置为参考,可以把arr矩阵分成4块:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 1]
(2) 使用前缀和矩阵
输入两个查询坐标(x1,y1)和(x2,y2),输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和
在arr矩阵中以这两个坐标为参考,可把矩阵分成4块:
子矩阵的和 = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

细节处理:

参考上一题

代码实现:

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

int main() 
{
   int n, m, q;
   cin >> n >> m >> q;
   // 输入数据
   vector<vector<long long>> arr(n+1, vector<long long>(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] + arr[i][j] - dp[i-1][j-1];

    // 使用前缀和数组
    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;
    }
}

724.寻找数组的中心下标

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

解法1:暴力求解,O(N^2)。遍历数组,每次固定一个下标,再分别计算左右两边区间的和,进行比较,绝对超时

解法2:使用前缀和思想
(1) 预处理
由于会涉及前后两个区间的求和所以在预处理时定义一个f表示前缀和数组,g表示后缀和数组
在这里插入图片描述
(2) 使用数组
然后只需要遍历一遍数组,每次固定一个下标,判断 f[i] == g[i] ,如果相等则返回 i,否则返回 -1

细节问题:

(1) 根据 f 和 g 的递推公式,填 f[0] 时会越界,填 g[n-1] 时,也会越界,所以要先初始化 f[0] = g[n-1] = 0
(2) 在填dp表时,前缀和数组f从前往后填,后缀和数组g从后往前填

代码实现:

class Solution 
{
public:
    int pivotIndex(vector<int>& nums) 
    {
        int n = nums.size();
        // 预处理前缀和,后缀和数组
        vector<int> f(n);
        vector<int> g(n);
        f[0] = 0, g[n-1] = 0; // 初始化

        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];
        
        // 使用数组
        for(int k = 0; k < n; k++)
            if(f[k] == g[k])
                return k;
        
        return -1;
    }
};

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

在这里插入图片描述
在这里插入图片描述

算法原理:

这道题和上一题基本上是一模一样的,也使用前缀和思想,只是本题是算乘法,上一题是算加法

(1) 预处理
由于会涉及前后两个区间的乘积所以在预处理时定义一个f表示前缀积数组,g表示后缀积数组
在这里插入图片描述
(2) 使用数组
在这里插入图片描述

细节问题:

(1) 根据 f 和 g 的递推公式,填 f[0] 时会越界,填 g[n-1] 时,也会越界,所以要先初始化 f[0] = g[n-1] = 1
(2) 在填dp表时,前缀和数组f从前往后填,后缀和数组g从后往前填

代码实现:

class Solution 
{
public:
    vector<int> productExceptSelf(vector<int>& nums) 
    {
        // 预处理前缀积,后缀积数组
        int n = nums.size();
        vector<int> f(n), g(n);
        f[0] = g[n-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];

        // 使用数组
        vector<int> ret;
        for(int k = 0; k < n; k++)
            ret.push_back(f[k] * g[k]);

        return ret;
    }
};

560.和为k的子数组

在这里插入图片描述
在这里插入图片描述

算法原理:

我们在 [滑动窗口] 系列中做过一道和本题十分类似的,只不过那道题目中的数据都是正整数,一直累加,结果是单调递增的,所以才可以用 “滑动算法"思想(本质就是"同向双指针”)进行优化。但是这道题中有0和负数,相加的结果不是单调递增的,不能使用 “滑动窗口”

这道题可以使用:前缀和+哈希表
我们引入"以 i 位置为结尾的所有的子数组",如图,所以可以把题目中要求的"和为 k 的子数组",转化成:在区间 [0, i-1] 中,有多少个前缀和等于 sum[i] - k
如何快速的统计前缀和个数呢?就要使用哈希表,把每次计算的 sum[i] - k 都扔进哈希表中,同时统计次数

下图里绿色部分的表示前缀和
在这里插入图片描述

细节问题:

(1) 前缀和加入哈希表的时机
在计算 i 位置之前,哈希表里面只保存 [0, i-1] 位置的前缀和
(2) 不用真的创建一个前缀和数组
用一个变量 sum 来标记前一个位置的前缀和即可
(3) 如果整个前缀和等于 k 呢
要在创建哈希表后初始化:hash[0] = 1。就相当于数组默认有一个前缀和为0的子数组

代码实现:

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 e : nums)
        {
            sum += e; // 计算当前位置的前缀和
            if(hash.count(sum - k)) ret += hash[sum - k]; // 统计个数
            hash[sum]++; // 把当前的前缀和扔进哈希表
        }
        return ret;
    }
};

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

在这里插入图片描述
在这里插入图片描述

算法原理:
这道题和上一题太相似了,并且也不能用"滑动窗口"优化,原因同上

在解决这题之前,先补充两个涉及的知识
(1) 同余定理若两数之差整除 p,则这两个数分别对 p 取模,余数相等
在这里插入图片描述
(2) C++中,[负数 % 正数] 的结果以及修正
在C++中,[负数 %(取模) 正数] 的结果是一个负数,如果想把这个负数修正为正确的正数
在这里插入图片描述

与上一题相同,本题还是使用:前缀和+哈希表
我们也引入"以 i 位置为结尾的所有的子数组",如图,所以可把本题中要求的"和可被 k 整除的非空子数组的数目"转化为:在 [0, i-1] 区间中,找到有多少个前缀和的余数等于 (sum % k + k) % k
如何快速的统计前缀和个数呢?就要使用哈希表。把每次计算的 (sum % k + k) % k 都扔进哈希表中,同时统计次数

下图里绿色部分的表示前缀和
在这里插入图片描述

细节问题:
参考上一题

代码实现:

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 x : nums)
        {
            sum += x;
            int r = (sum % k + k) % k; // 修正后的余数
            if(hash.count(r) ret += hash[r]; // 统计结果
            hash[r]++;
        }
        return ret;
    }
};

525.连续数组

在这里插入图片描述
在这里插入图片描述

算法原理:

这道题如果按题目要求直接找相同数量0和1的最长子数组会难度很大。但是如果我们把数组中所有的 0 改成 -1,本题就可以转换成:在数组中,找出最长的子数组,使子数组中所有元素和为0。那它的算法思路就和前文的 [560.和为 k 的子数组] 一模一样了

转化后,使用:前缀和+哈希表
具体的算法思路参考 [560.和为 k 的子数组]

魔鬼细节问题:

(1) 哈希表里存什么?
根据题目要求,存的应该是前缀和的最大长度(下标)
(2) 什么时候存入哈希表?
和上面一样,在计算 i 位置之前,丢进哈希表
(3) 如果有重复的 < sum, i>,如何存?
就是在遍历到 i 位置时,发现前面 j 位置的前缀和也等于 sum ,要把两个 sum 都入哈希表吗?不需要,因为要求最大长度只保留前面的那一对 < sum, i>
在这里插入图片描述
(4) 默认的前缀和为0的情况,如何存?
创建哈希表后,初始化:hash[0] = -1。
在这里插入图片描述
(5) 长度怎么算?
因为 [i, j] 是闭区间,计算区间元素是 i - j + 1个,但是这里多算了一个 j 位置,所以还要 -1
在这里插入图片描述

代码实现:

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

        int sum = 0, ret = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            // 计算当前位置前缀和,把 -1 变 0
            sum += nums[i] == 0 ? -1 : 1; 

            // 如果已经存在了,就不入哈希,更新长度,不存在就入哈希
            if(hash.count(sum)) ret = max(ret, i - hash[sum]);
            else hash[sum] = i;
        }
        return ret;
    }
};

1314.矩阵区域和

在这里插入图片描述
在这里插入图片描述

算法原理:

先理解题目意思
在这里插入图片描述

很显然,本题要使用二维前缀和。
二维前缀和的预处理矩阵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>> ans(m, vector<int>(n));
        int x1 = 0, y1 = 0, x2 = 0 ,y2 = 0;
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                x1 = max(0, i-k) + 1, y1 = max(0, j-k) + 1;
                x2 = min(i+k, m-1) + 1, y2 = min(j+k, n-1) + 1;
                ans[i][j] = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1];
            }
        }
        return ans;
    }
};

三,算法总结

通过上面若干道题目要掌握使用前缀和的两个步骤:一是预处理前缀和数组,这里要先明确 dp[i][j] 的含义,再推导递推公式,二是使用前缀和数组,其实就是在预处理好的 dp 数组中直接拿值,这一步的时间复杂度是O(1)。要注意的细节点就是使用原数组填 dp 数组的边界问题

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

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

相关文章

可能一拆为二,英特尔为何走到今天这一步?

【科技明说 &#xff5c; 科技热点关注】 近来看到外媒消息说&#xff0c;英特尔迫于经营压力&#xff0c;也不得不铤而走险&#xff0c;欲将英特尔一分为二&#xff0c;即芯片制造与芯片设计分离开&#xff0c;互相剥离&#xff0c;独立发展。 于是乎&#xff0c;英特尔将分拆…

图卷积神经网络GNN(一)

图卷积神经网络GNN 研究学习的背景 对于图神经网络&#xff08;GNN&#xff09;来输入的数据是图。&#xff08;解决输入数据不规则情况&#xff09;输入的格式不是固定的 研究涵盖&#xff1a;节点分类&#xff08;nodeclassification&#xff09;、边预测&#xff08;link…

​ArcGIS Pro和ArcGIS的10大区别

本文来源&#xff1a;水经注GIS公众号 如果你经常使用ArcGIS 进行制图和分析&#xff0c;那么你一定听说过ArcGIS Pro&#xff0c;这款软件是Esri未来主打的一款桌面GIS软件&#xff0c;那么这款软件和ArcGIS相比有什么不同呢&#xff0c;这里为你列举了两款软件的10大区别&am…

海康威视相机在QTcreate上的环境配置教程(qt+opencv+海康SDK)

环境配置教程 前言&#xff1a;环境配置&#xff1a;1.海康SDK2.opencv 参考导入文件 前言&#xff1a; 配置环境是编程的第一步&#xff0c;所以写这篇文章来指导环境的配置。如果已经配置好了&#xff0c;想在qt上使用海康的摄像头&#xff0c;可以参考这篇文章&#xff1a;…

骨传导耳机哪个品牌好用?良心测评推荐5大高分骨传导耳机!

在快节奏、数字化的生活时代&#xff0c;耳机成为连接外界与个人世界的桥梁&#xff0c;尤其在户外运动和健身场景中更是不可或缺。传统入耳式耳机虽然携带方便、音质优秀&#xff0c;但长时间佩戴会对耳道和鼓膜造成压力&#xff0c;甚至引发耳部不适。为解决这一痛点&#xf…

windows下自启springboot项目(jar+nginx)

1、将springboot项目打包为jar 2、新建文本文档 test.txt&#xff0c;并输入 java -jar D:\test\test.jar&#xff08;修改为自己的jar包位置&#xff09; 保存 然后修将后缀名改为 .bat 3、在同一目录再新建 文本文档test.txt&#xff0c;输入以下内容&#xff0c;&…

Meme“淘金”热潮下:Meme发射平台的安全风险分析

2023年&#xff0c;Meme赛道成为加密市场和各大公链生态的重点关注板块之一&#xff0c;尤其是在Solana等公链上&#xff0c;Meme代币迎来了爆发。许多Meme代币的交易量飙升&#xff0c;年初Solana生态中的Meme代币交易额甚至达到百亿美元。乘着Meme代币的东风&#xff0c;Meme…

VSCode配置 C/C++ 开发环境

目录 1.概述 2. 安装 C/C 扩展 3. 安装编译器 3.1.Windows 3.2.macOS 3.3.Linux 4. 配置 VSCode 5. 构建和运行 1.概述 在 Visual Studio Code (VSCode) 中配置 C/C 开发环境&#xff0c;需要安装一些扩展和正确配置编译器。以下是详细的步骤&#xff1a; 2. 安装 C/C…

Agilent MSO7104A安捷伦示波器1G 4通道

Agilent MSO7104A安捷伦示波器1G 4通道 附加功能&#xff1a; 带宽&#xff1a;1 GHz 通道&#xff1a;4 个模拟和 16 个数字 采样率&#xff1a;4 GSa/s 记录长度&#xff1a;标准 8 Mpts MegaZoom III 深存储器 垂直分辨率&#xff1a;8位 自动缩放和峰值检测 有见地的…

数据分类:成功DLP的基本要求

现如今我们存储数据的方式发生了巨大变化&#xff0c;从优质硬盘到今天的云存储解决方案&#xff0c;存储技术的进步大幅削减了成本&#xff0c;并使组织能够呈指数级增长的数据&#xff0c;我们都在利用这一点。 事实上&#xff0c;数据每年以惊人的 25% 的速度增长&#xff…

顶级PCVR头显具备什么配置?清晰度如何?

头戴式显示器是VR体验中用户与虚拟现实环境交互不可或缺的一部分。高端的头显通常可为使用者提供具有超高分辨率的高清画面&#xff0c;因此当你拥有一部高端的VR头显&#xff0c;虚拟世界将如真实世界一样呈现在你眼前。 说到顶级头显我们就不得不提到Varjo&#xff0c;Varjo是…

最全的性能测试性能诊断工具清单

给做性能问题的诊断需要用到的工具分一下类的话&#xff0c;主要有以下类&#xff0c;网络通信报文抓取工具、基础软件监控工具、第三方资源监控工具、专业性能诊断工具和反编译工具。 接下来我们就分别说说&#xff0c;每类里面都有哪些工具。首先第一类是网络通信报文抓取工…

【CanMV K230 AI视觉】人脸3D网格

【CanMV K230 AI视觉】人脸3D网格 人脸3D网格 &#xff08;动态测试效果可以去下面网站自己看。&#xff09; B站视频链接&#xff1a;已做成合集 抖音链接&#xff1a;已做成合集 人脸3D网格 人脸3D网格&#xff0c;在检测到人脸后用多个点描绘整个脸&#xff0c;从而把人脸…

Java导入、导出excel保姆级教程(附封装好的工具类)

前言 我们在日常开发中&#xff0c;一定遇到过要将数据导出为Excel的需求&#xff0c;那么怎么做呢&#xff1f;在做之前&#xff0c;我们需要思考下Excel的组成。Excel是由四个元素组成的分别是&#xff1a;WorkBook(工作簿)、Sheet(工作表)、Row(行)、Cell(单元格)&#xff…

深度学习-目标检测(四)-Faster R-CNN

目录 一.模型框架 二&#xff1a;步骤详细 1.conv layers 2.RPN 3.anchors 4.cls layer分类 5.reg layer回归 6.Proprosal 7.Rol pooling 8.Classification 三.训练 1.训练RPN网络 2.全连接层部分训练&#xff1a; 都看到这里了&#xff0c;点个赞把&#xff01;&a…

adb有线连接正常,adb connect失败

adb connect失败 1. 确认两个设备在同一个局域网 2. 确认此网络是否有adb连接的权限(有的公司网络不允许adb) 3. 确认防火墙设置 如果前面3步都确认没问题&#xff0c;Ping ip也能成功&#xff0c;那么有可能就是端口的问题: step1&#xff1a; 先用有线连接设备&#xff0…

DBeaver连接数据库报连接错误:Public Key Retrieval is not allowed

问题描述 使用DBeaver软件连接mysql数据库的时候&#xff0c;有如下提示信息&#xff1a; 解决办法 点击驱动属性->找到allowPublicKeyRetrieval这项&#xff0c;把值设置为TRUE,再点击连接测试 可以成功连接到mysql数据库&#xff0c;问题解决~

面试题 Spring bean 循环依赖解决方案以及三级缓存讲解

文章目录 Spring bean 循环依赖1.1 什么是循环依赖1.2 Spring循环依赖几种情况 什么是三级缓存到底是什么东西&#xff0c;三级缓存做了什么&#xff1f;三级缓存源码讲解 Spring bean 循环依赖 1.1 什么是循环依赖 当面试官问到你给我讲一下什么是循环依赖吧&#xff0c;该如…

简单数据库sqlite

目录 数据库 简介 1、分类&#xff1a; 大型 中型 小型 2、名词&#xff1a; 3、嵌入式数据库&#xff1a; 4、sqlite3的安装&#xff1a; LTS long term support 5.1、sqlite3的使用&#xff1a; 0、启动sqlite3 1、系统维护命令&#xff1a;> .help 5.2、标准SQL…

盘点4款高效率的PDF在线编辑工具

PDF格式的文件在分享和传输这方面确实要比七个格式的文件要方便一些&#xff0c;但正因为如此&#xff0c;导致有的时候编辑起来不是很方便。这个时候专业的PDF编辑工具就变得很重要&#xff0c;尤其是在线编辑工具&#xff0c;不用下载就可以直接使用。所以我便要跟大家分享几…