算法魅力之牛叉的前缀和

1.什么是前缀和

前缀和算法(Prefix Sum Algorithm) 是一种常用的算法技巧,用于快速计算数组的某些子数组的和。它通过提前计算出数组中元素的累加和,来加速后续的区间和查询,特别适用于需要频繁查询子数组和的场景。

前缀和的基本思想

给定一个数组 A,前缀和数组 S 是通过将 A 中的每个元素累加得到的数组,具体来说,S[i] 存储的是 A[0]A[i] 的和。

  • 前缀和数组的定义:

    S[i] = A[0] + A[1] + ... + A[i]

  • 用前缀和数组来求区间和: 通过前缀和数组,我们可以非常快速地求出任意区间 [L, R] 的和,公式为:

    sum(L, R) = S[R] - S[L-1]

    其中 S[R] 是从 A[0]A[R] 的累加和,S[L-1] 是从 A[0]A[L-1] 的累加和,所以 S[R] - S[L-1] 就是 A[L]A[R] 的区间和。

具体步骤

  1. 构建前缀和数组:

    • 初始化前缀和数组 S,其中 S[0] = A[0]
    • 对于 i > 0,有:S[i] = S[i-1] + A[i]
  2. 查询区间和:

    • 对于任意区间 [L, R],通过公式 sum(L, R) = S[R] - S[L-1] 计算区间和。

优点

  • 查询效率高:使用前缀和数组,区间和查询的时间复杂度为 O(1),即常数时间。这使得处理大量区间和查询时非常高效。
  • 预处理时间:构建前缀和数组的时间复杂度为 O(n),其中 n 是数组的长度。

缺点

  • 空间复杂度:需要额外的空间来存储前缀和数组,空间复杂度为 O(n)。
  • 适用场景:前缀和算法主要适用于静态数组或不经常更新的数组。如果数组频繁更新,前缀和算法的效率将受到影响,因为每次更新可能需要重新计算前缀和数组。

基本应用场景

  1. 区间和查询:当你需要频繁查询一个数组的区间和时,前缀和算法是一个非常高效的解决方案。
  2. 区间最小值/最大值查询:虽然前缀和主要用于求和,但其思想也可以扩展到其他的区间查询问题,如查询区间的最大值或最小值。
  3. 二维数组问题:前缀和不仅适用于一维数组,也可以扩展到二维数组(矩阵),用于快速计算矩阵中任意子矩阵的和。

2.前缀和练习

 2.1 一维前缀和(模版)

287724f941b141569c05b47a2b5a9e52.png

示例:

输入:

3 2
1 2 4
1 2
2 3

输出:

3
6

这个题就是典型的求区间和

代码展示+算法思路

注意:题目l是大于等于1的,所以我们输入的数组是从1开始输入。

#include <iostream>
using namespace std;
#include <vector>
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> sum(n+1);for(int i=1;i<=n;i++)sum[i]=sum[i-1]+arr[i];for(int j=0;j<q;j++){int l=0,r=0;cin>>l>>r;cout<<sum[r]-sum[l-1]<<endl;}return 0;
}

sum 数组是前缀和数组,sum[i] 存储的是从 arr[1]arr[i] 的元素之和。

  • 初始时,sum中的元素被设置为0
  • 然后通过循环逐个计算前缀和:sum[i] = sum[i - 1] + arr[i],也就是每个位置的前缀和是前一个位置的前缀和加上当前元素。
区间查询:对于每个查询 [l, r],通过前缀和公式 sum[r] - sum[l-1] 来求得区间和。这样,单次查询的时间复杂度为 O(1)。
总时间复杂度: O(n + q),其中 n 是数组的大小, q 是查询的数量。

2.2 二维前缀和典型模版

4f5a3041a43c4a74801dfd1f652b4c61.png

算法思路分析

类比于一维数组的形式,如果我们能处理出来从 [0, 0] 位置到 [i, j] 位置这片区域内所有
元素的累加和,就可以在 O(1) 的时间内,搞定矩阵内任意区域内所有元素的累加和。因此我们
接下来仅需完成两步即可:
第一步:搞出来前缀和矩阵
这里就要用到一维数组里面的拓展知识,我们要在矩阵的最上面和最左边添加上一行和一列
0,这样我们就可以省去非常多的边界条件的处理。也可以满足从题目中(1,1)开始。处理后的矩阵就像这样:

e44be8e76b804ceb90ebf7b16d1b940a.png

 填写前缀和矩阵数组的时候,下标直接从 1 开始,能大胆使用 i - 1 , j - 1 位置的值。 注意 dp 表与原数组 matrix 内的元素的映射关系: i. 从 dp 表到 matrix 矩阵,横纵坐标减一; ii. 从 matrix 矩阵到 dp 表,横纵坐标加一。

前缀和矩阵中 dp[i][j] 的含义,以及如何递推二维前缀和方程
dp[i][j] 的含义:
dp[i][j] 表示,从 [0, 0] 位置到 [i, j] 位置这段区域内,所有元素的累加和。对应
下图的红色区域:
cf488af38d804bb4b56bfd8ef979f835.png

969ff7910f5a41f49163c9dcfb68d939.png 使用前缀和矩阵

[x1,y1]----[x2,y2]的和

65f359e2dc5142ddb34f3d206d3894da.png

代码展示

#include <iostream>
#include <vector>
using namespace std;
int main() {int n,m,q;int arr[1010][1010];long long dp[1010][1010];cin>>n>>m>>q;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)cin>>arr[i][j];}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[x2][y1-1]-dp[x1-1][y2]+dp[x1-1][y1-1]<<endl;}
}

 还是采用的c语言形式定义二维数组比较简便,因为题目给出了行列的数据范围,所以我们直接定义1010为数组行列的个数,初始化都为0 ,不影响加的结果,当然后续遍历也不会用到多余的0数据。

2.3 寻找数组的中心下标 

724. 寻找数组的中心下标 - 力扣(LeetCode)

1549fd3cb45c48769927a7984a41b22e.png

  通过读取题意就是求i前后区间的和判断是否相等,返回i即可。

因此,我们可以 先预处理出来两个数组,一个表示前缀和,另一个表示后缀和。
然后,我们可以用一个 for 循环枚举可能的中心下标,判断每一个位置的「前缀和」以及 「后缀和」,如果二者相等,就返回当前下标。

9607802395414bf5a9dfb5c45edd9037.png

class Solution {
public:int pivotIndex(vector<int>& nums) {int n=nums.size();vector<int> v1(n),v2(n);for(int i=1;i<n;i++)v1[i]=v1[i-1]+nums[i-1];for(int i=n-2;i>=0;i--)v2[i]=v2[i+1]+nums[i+1];for(int i=0;i<n;i++){if(v1[i]==v2[i])return i;}return -1;}
};
v1[i] 表示:[0, i - 1] 区间所有元和
v2[i] 表示:[i + 1, n - 1] 区间所有元素的和

2.4 除自身以外数组的乘积 

238. 除自身以外数组的乘积 - 力扣(LeetCode)

62f450c0154242df818df027c50cf143.png

注意题目的要求,不能使用除法,并且要在 O(N) 的时间复杂度内完成该题。那么我们就不能使
用暴力的解法,以及求出整个数组的乘积,然后除以单个元素的方法。
继续分析,根据题意,对于每一个位置的最终结果 ret[i] ,它是由两部分组成的:
i. nums[0] * nums[1] * nums[2] * ... * nums[i - 1]
ii. nums[i + 1] * nums[i + 2] * ... * nums[n - 1]
于是,我们可以利用前缀和的思想,使用两组 v1和 v2,分别处理出来两个信息:.
v1 表示:i 位置之前的所有元素,即 [0, i - 1] 区间内所有元素的前缀乘积,
v2表示: i 位置之后的所有元素,即 [i + 1, n - 1] 区间内所有元素的后缀乘积
然后再处理最终结果。
e42f3c3f19be4659bc589fc7d9433f28.png
class Solution {
public:vector<int> productExceptSelf(vector<int>& nums) {vector<int >v;int n=nums.size();vector<int>v1(n,1);vector<int>v2(n,1);for(int i=1;i<n;i++)v1[i]=v1[i-1]*nums[i-1];for(int i=n-2;i>=0;i--)v2[i]=v2[i+1]*nums[i+1];for(int i=0;i<n;i++)v.push_back(v1[i]*v2[i]);return v;}
};

 本题其实和上一道题思路很相似,将前后前缀和换成了前后前缀积

2.5 和为K的子数组

560. 和为 K 的子数组 - 力扣(LeetCode)

7f6d0bb34441420f83c7820211343627.png

暴力枚举i区间内所有的子数组

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

但是会重复计算很多次相同数字的和 ,简单优化求出所有前缀和存放在数组中,在暴力枚举所有区间,统计等于k的个数。

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

 哈希+前缀和,将前缀和存在哈希表中。

设 i 为数组中的任意位置,用 sum[i] 表⽰ [0, i] 区间内所有元素的和。
想知道有多少个「以 i 为结尾的和为 k 的子数组」,就要找到有多少个起始位置为 x1, x2,
x3... 使得 [x, i] 区间内的所有元素的和为 k 。那么 [0, x] 区间内的和就是sum[i] - k 了。于是问题就变成:
找到在 [0, i - 1] 区间内,有多少前缀和等于 sum[i] - k 的即可。
不用真的初始化一个前缀和数组,因为只关⼼在 i 位置之前,有多少个前缀和等于
sum[i] - k 。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种
前缀和出现的次数。
当我们的整个数组和=k时,则sum-k=0  则我们先定义一个hash[0]=1,因为不存在【0,-1】这个区间。
class Solution {
public:int subarraySum(vector<int>& nums, int k) {unordered_map<int,int> hash;hash[0]=1;int sum=0;int ret=0;for(auto e: nums ){sum+=e;if(hash.count(sum-k))ret+=hash[sum-k];hash[sum]++;}return ret;}
};

结束语

本节内容就到此结束了,前缀和的题目还有很多,后续有时间也会继续更新前缀和的相关题目分享,也欢迎友友一起讨论。

最后,感谢各位友友的支持!!!

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

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

相关文章

Java JVM(内存结构,垃圾回收,类加载,内存模型)

一、JVM 主要功能 1. 什么是 jvm&#xff1f; JVM&#xff08;Java Virtual Machine)&#xff1a;负责运行 Java 程序的核心组件。它将 Java 字节码&#xff08;.class 文件&#xff09;解释或编译为机器代码&#xff0c;并提供内存管理、垃圾回收和线程管理等功能。 JRE (J…

机器学习基础之集成学习

集成学习&#xff08;Ensemble Learning&#xff09;是一种强大的机器学习方法&#xff0c;它通过结合多个模型的预测结果来提高整体的学习效果。集成学习方法在许多实际应用中表现出了优秀的性能&#xff0c;尤其在处理复杂问题时&#xff0c;它常常能够比单一模型取得更好的结…

33 基于单片机的智能窗帘控制系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DHT11温湿度传感器检测温湿度&#xff0c;滑动变阻器连接ADC0832数模转换器转换模拟,光敏传感器&#xff0c;采用GP2D12红外传感器&#xff0c;通过LCD1602显示屏显示…

使用docker-compese部署SFTPGo详解

官网&#xff1a;SFTP & FTP as a Managed Service (SaaS) and On-premise 一、SFTPGo简介 SFTPGo 是一款功能强大的文件传输服务器软件。它支持多种协议&#xff08;SFTP、SCP、FTP/S、WebDAV、HTTP/S&#xff09;和多个存储后端。 借助 SFTPGo&#xff0c;您可以利用本地…

我与Linux的爱恋:消息队列

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;Linux的学习 文章目录 消息队列的引入以及基本概念**​消息队列的基本概念** 消息队列与命名管道和共享内存的不同消息队列的原理消息队列工作流程 System V 消息队列的主要函数msggetms…

LWIP和FATFS 实现 FTP 服务端

目录 一、前言 二、LWIP 和 FTP 简介 1.LWIP 2.FTP 三、实现 FTP 服务端的主要步骤 1.初始化 LWIP 2.创建 FTP 服务器任务 3.处理客户端连接 4.实现 FTP 命令处理 5.文件系统操作 6.错误处理和日志记录 四、示例代码 1.创建FTP任务 2. FTP任务代码 3.处理交互数据…

Java基础访问修饰符全解析

一、Java 访问修饰符概述 Java 中的访问修饰符用于控制类、方法、变量和构造函数的可见性和访问权限&#xff0c;主要有四种&#xff1a;public、protected、default&#xff08;无修饰符&#xff09;和 private。 Java 的访问修饰符在编程中起着至关重要的作用&#xff0c;它…

llvm源码编译

0x00 获取llvm源码 获取llvm项目源码&#xff1a;git clone https://github.com/llvm/llvm-project.git 但是&#xff0c;该项目较大&#xff0c;且直接从github下载源码可能会超时失败。可利用gitee的镜像项目进行clone&#xff1a;git clone --depth 1 https://gitee.com/m…

SpringBoot源码-Spring Boot启动时控制台为何会打印logo以及自定义banner.txt文件控制台打印

1.当我们启动一个SpringBoot项目的时候&#xff0c;入口程序就是main方法&#xff0c;而在main方法中就执行了一个run方法。 SpringBootApplication public class StartApp {public static void main(String[] args) {// testSpringApplication.run(StartApp.class);} }publi…

Uniad复现学习

在优云平台部署训练&#xff0c;加速训练。 关于UCloud(优刻得)旗下的compshare算力共享平台 UCloud(优刻得)是中国知名的中立云计算服务商&#xff0c;科创板上市&#xff0c;中国云计算第一股。 UCloud&#xff08;优刻得&#xff09;旗下的Compshare算力共享平台具有以下优点…

数学建模——Topsis法

数模评价类&#xff08;2&#xff09;——Topsis法 概述 Topsis:Technique for Order Preference by Similarity to Ideal Solution 也称优劣解距离法&#xff0c;该方法的基本思想是&#xff0c;通过计算每个备选方案与理想解和负理想解之间的距离&#xff0c;从而评估每个…

基于单片机的四位数码管检测有毒气体

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过滑动变阻器连接ADC0832数模转换器模拟有毒气体浓度检测&#xff0c;通过数码管实时显示&#xff0c;如果超过阈值&#xff0c;则蜂鸣器报警&#xff0c;灯光亮起。按…

小程序 - 比较数字大小

小程序交互练习 - 比较数字大小的小程序 目录 比较数字大小 功能描述 准备工作 页面内容 设置页面事件 页面绑定事件 比较大小 按钮绑定事件 比较事件 设置结果显示 页面样式 功能截图 总结 比较数字大小 本案例将实现“比较数字大小”微信小程序&#xff0c;它的…

windows下用mysqld启动免安装mysql

windows系统可以下载免安装版本&#xff0c;就是绿色版&#xff0c;里面包含mysql运行的所有必要条件。 ![[Pasted image 20241128231459.png]] 启动步骤&#xff1a; 解压&#xff0c;然后在解压目录创建my.ini。 [mysqld] # 设置13306端口 port13306# 设置mysql的安装目录…

windows安装itop

本文介绍 win10 安装 itop 安装WAMP集成环境前 先安装visual c 安装itop前需要安装WAMP集成环境(windowsApacheMysqlPHP) 所需文件百度云盘 通过网盘分享的文件&#xff1a;itop.zip 链接: https://pan.baidu.com/s/1D5HrKdbyEaYBZ8_IebDQxQ 提取码: m9fh 步骤一&#xff1…

Leetcode - 周赛425

目录 一&#xff0c;3364. 最小正和子数组 二&#xff0c; 3365. 重排子字符串以形成目标字符串 三&#xff0c;3366. 最小数组和 四&#xff0c;3367. 移除边之后的权重最大和 一&#xff0c;3364. 最小正和子数组 本题可以直接暴力枚举&#xff0c;代码如下&#xff1a; …

微服务即时通讯系统的实现(服务端)----(2)

目录 1. 语音识别子服务的实现1.1 功能设计1.2 模块划分1.3 模块功能示意图1.4 接口的实现 2. 文件存储子服务的实现2.1 功能设计2.2 模块划分2.3 模块功能示意图2.4 接口的实现 3. 用户管理子服务的实现3.1 功能设计3.2 模块划分3.3 功能模块示意图3.4 数据管理3.4.1 关系数据…

Matlab Simulink HDL Coder开发流程(一)— 创建HDL兼容的Simulink模型

创建HDL兼容的Simulink模型 一、使用Balnk DUT模板二、从HDL Coder库中选择模块三、为DUT开发算法/功能四、为设计创建Testbench五、仿真验证设计功能六、Simulink模型生成HDL代码 这个例子说明了如何创建一个用于生成HDL代码的Simulink模型。要创建兼容HDL代码生成的MATLAB算法…

mfc110u.dll是什么意思,mfc110u.dll丢失解决方法大全详解

mfc110u.dll是Microsoft Foundation Classes (MFC)库的一个特定版本&#xff08;版本11.0&#xff09;的Unicode动态链接库文件。MFC是Microsoft为C开发者设计的一个应用程序框架&#xff0c;主要用于简化Windows应用程序的开发工作。这个框架封装了很多Windows API函数&#x…

debian 11 虚拟机环境搭建过坑记录

目录 安装过程系统配置修改 sudoers 文件网络配置换源安装桌面mount nfs 挂载安装复制功能tab 无法补全其他安装 软件配置eclipse 配置git 配置老虚拟机硬盘挂载 参考 原来去 debian 官网下载了一个最新的 debian 12&#xff0c;安装后出现包依赖问题&#xff0c;搞了半天&…