【Java算法专场】前缀和(下)
目录
和为 K 的子数组
算法分析
算法步骤
算法代码
算法示例
和可被 K 整除的子数组
算法分析
同余定理
负数取余
算法步骤
算法代码
算法示例
连续数组
算法分析
算法步骤
算法代码
算法示例
矩阵区域和
算法分析
算法步骤
算法代码
算法示例

算法分析
本道题是要在数组中查找和为k的子数组,如果我们使用暴力解法,时间复杂度为O(N^2)。对于这种求子数组和的问题,我们可以使用前缀和来解决。
算法步骤
-
初始化:定义一个sum,初始化为0,用来计算数组中元素和;定义一个HashMap用来存储数组中的前缀和,还有值的出现次数(此处需要添加(0,1),若sum-k刚好等于0的情况)。定义一个ans用来计算数组中有多少个和为k的子数组。
-
遍历数组:将数组中的元素累加到sum中,同时在HashMap中判断此时的sum是否存在,若存在,则将其键值添加到ans中。当添加完之后,将此时的sum添加到Hashmap中。
-
返回ans:当遍历完数组,此时返回ans即可。
算法代码
/*** 计算一个数组中连续子数组和等于给定值k的子数组个数* 该方法使用前缀和的概念来加速计算过程,通过HashMap记录每个前缀和出现的次数** @param nums 整数数组,其中nums[i]表示子数组的元素* @param k 目标和,表示连续子数组的和* @return 返回连续子数组和等于k的子数组个数*/public int subarraySum(int[] nums, int k) {// 初始化当前前缀和为0int sum=0;// 初始化结果变量为0,用于记录找到的子数组个数int ans=0;// 使用HashMap来存储每个前缀和出现的次数HashMap<Integer,Integer> hash=new HashMap<>();// 将前缀和0的出现次数设置为1,表示前缀和为0的出现了一次hash.put(0,1);// 遍历数组中的每个元素for(int x:nums){// 累加当前元素到前缀和sum+=x;// 如果当前前缀和减去目标和的值已经出现过,则说明找到了一个或多个和为k的子数组// 将其出现次数加到结果变量中ans+=hash.getOrDefault(sum-k,0);// 更新HashMap,记录当前前缀和的出现次数// 如果当前前缀和不存在,则默认为0,然后加1hash.put(sum,hash.getOrDefault(sum,0)+1);}// 返回找到的子数组个数return ans;}
时间复杂度为O(n),n为数组长度,只遍历一遍数组,hash的查找速度为O(1).
空间复杂度为O(n),最坏情况下hash存储的个数刚好是n个,n是数组长度。
算法示例
以nums = [1,2,3], k = 3为例
第一步:初始化
sum=0,ans=0,hash={(0,1)}
第二步:遍历数组
- sum+=1,ans+=0,hash={(0,1),(1,1)}
- sum=3, sum-k=0,此时hash中key为0的键值为1,ans+1,ans=1,hash={(0,1),(1,1),(3,1)}
- sum=6,sum-k=3,此时hash中key为3的键值为1,ans+1,ans=2,hash={(0,1),(1,1),(3,1),(6,1)}.
第三步:返回ans
此时ans=2,返回即可。
和可被 K 整除的子数组

算法分析
本道题与上一道题类似,不过这道题求和能被K整除的子数组。若使用暴力解法,时间复杂度为O(n^2)。但我们可以使用更好的方法来优化。那就是前缀和。
在讲这道题之前,我们先来了解一下什么是同余定理
同余定理
给定一个正整数 m,如果两个整数 a 和 b 满足 m|(a-b),即 a-b 能够被 m 整除,或者 (a-b)/m 得到一个整数,那么就称整数 a 与 b 对模 m 同余,记作 a≡b(mod m)。对模 m 同余是整数的一个等价关系。这里我们不做过多介绍(想要了解的更清楚可以去查一下)。
(A-B)%m=0 <==> A%m=B%m
示例:(5-3)%2=0 <=> 5%2=1 3%2=1
负数取余
负数%整数=负数
但我们这里需要的正数,所以我们可以在A%m之后再加上m之后就可以得到一个正数。同时,可能A本身是一个正数,那么取余之后再加上m,就有点多余了,所有我们这里在+m之后还需对其再模一次m,即(A%m+m)%m.
算法步骤
- 初始化:定义一个ans并初始化为0;定义一个sum并初始化为0,用来累加数组和;定义一个HashMap,Key用来记录余数,values用来记录余数出现的次数。
- 遍历数组:让sum累加数组中的元素。同时,当sum累加一个元素之后,此时需要在hashmap查看是否有以【(sum%k+k)%k】为键的键值对。如果当前前缀和的余数已经出现过,则说明找到了一个或多个能被k整除的子数组,则将此键值对下的键值加到ans中。当加完之后,将sum此时的余数映射到hashmap中,并将其键值+1.
- 处理细节:此处有可能sum的余数为0,所以我们需要在遍历之前先添加一个(0,1).
- 返回结果:当遍历完数组之后,此时返回ans即可。
算法代码
/*** 计算数组中前缀和能被k整除的子数组数量* * @param nums 输入的整数数组* @param k 整数k,用于判断子数组的和是否能被k整除* @return 返回满足条件的子数组数量*/public int subarraysDivByK(int[] nums, int k) {// 用于计算前缀和int sum=0;// 用于记录满足条件的子数组数量int ans=0;// 使用HashMap来存储每个前缀和的出现次数HashMap<Integer,Integer> hash=new HashMap<>();for(int x:nums){// 累加当前元素到前缀和sum+=x;// 如果当前前缀和减去目标和的值已经出现过,则说明找到了一个或多个和为k的子数组ans+=hash.getOrDefault((sum%k+k)%k,0);// 更新HashMap,记录当前前缀和的出现次数hash.put((sum%k+k)%k,hash.getOrDefault((sum%k+k)%k,0)+1);}}
时空复杂度为O(n),n为数组长度,整个过程,只遍历了一遍数组。在最坏情况下,可能hash的键值对有n个。
算法示例
以nums = [4,5,0,-2,-3,1], k = 5为例
第一步:初始化
sum=0,ans=0.hash={(0,1)};
第二步:遍历数组
(记余数为mod)
- sum+=nums[0]=4,mod=(4%5+5)%5=4,此时hash中不存在以4为键的键值对,添加到hash中,hash={(0,1),(4,1)}
- sum+=nums[1]=4+5=9,mod=(9%5+5)%5=4.此时hash存在以4为键的键值对,键值为1,此时ans+1=1,更新键值,hash={(0,1),(4,2)}
- sum+=nums[2]=9+0=9,mod=(9%5+5)%5=4,此时hash存在以4为键的键值对,键值为2,此时ans+2=3,更新键值,hash={(0,1),(4,3)}
- sum+nums[3]=9-2=6,mod=(7%5+5)%5=2,此时hash不存在以2为键的键值对,添加到hash中,hash={(0,1),(4,3),(2,1)}
- sum+nums[4]=7-3=4,mod=(4%5+5)%5=4,此时hash存在以4为键的键值对,键值为3,此时ans+3=6,更新键值,hash={(0,1),(4,4),(2,1)}
- sun+nums[5]=4+1=5,mod=(5%5+5)%5=0,此时hash存在以0为键的键值对,键值为1,此时ans+1=7,更新键值,hash={(0,2),(4,4),(2,1)}
第三步:返回结果
此时已经遍历完数组,ans=7,返回ans即可。
连续数组

算法分析
本道题与第一道和为k的子数组类似,但本道题要求的在一个二进制数组中找具有相同数量0和1的最长子数组。若用暴力解法,时间复杂度为O(n^2),但我们这里可以使用前缀和。让时间复杂度达到O(n).
算法步骤
- 初始化:定义ans并初始化为0,定义sum,用来计算每次遍历到的位置之前的差值;定义hash,用来存放sum以及其在数组中的索引。同时需要将以0为键的键值设置为-1,这是为了防止在0处就出现了相同数量的0和1.
- 遍历数组:在将nums[i]添加到sum时,如果nums[i]是0,那么就视为-1,反之,若是1,则直接累加到数组中。通过-1和1,我们就能算出0和1此时的差值。
- 查看0和1的数量:每次更新完sum的值,我们都需要判断sum是否已经在hash中;若已经在hash中,我们则直接更新ans的值,ans=Math.max(ans,i-hash.get(sum))。若此时sum不存在hash中,就将sum及其索引i添加到hash中,即hash.put(sum,i)。
- 返回结果:当我们遍历完数组,此时就可以将ans返回。
算法代码
/*** 寻找给定数组中最长的连续子数组,使得子数组中0和1的个数相等** @param nums 一个只包含0和1的整数数组* @return 返回满足条件的最长子数组的长度*/public int findMaxLength(int[] nums) {// sum用于记录数组遍历过程中的0和1的累积差值int sum=0;// ans用于记录满足条件的最长子数组的长度int ans=0;// 使用HashMap来存储每个累积差值首次出现的索引位置HashMap<Integer,Integer> hash=new HashMap<>();// 初始化sum为0,并将0作为HashMap的初始索引位置hash.put(0,-1);// 遍历数组,计算累积差值,并更新最长子数组的长度for(int i=0;i<nums.length;i++){// 如果当前元素为0,则将sum减1,否则将sum加1sum+=(nums[i]==0?-1:1);// 如果当前累积差值已经在HashMap中存在,则找到了一个满足条件的子数组if(hash.containsKey(sum)) {// 更新最长子数组的长度ans=Math.max(ans,i-hash.get(sum));} else {// 如果当前累积差值首次出现,则将其索引位置存入HashMaphash.put(sum,i);}}// 返回最长子数组的长度return ans;}
时空复杂度为O(n),n为数组长度,整个过程只遍历了一遍数组,在最坏情况下,hash的键值对可能是O(n).
算法示例
以nums = [0,1,0]为例
第一步:初始化
ans=0,sum=0,hash={(0,-1)};
第二步:遍历数组
- sum+=nums[0]=-1,此时hash中不存在以-1为键的键值对,将其添加到hash中,hash={(0,-1),(-1,0)}
- sum+=nums[1]=-1+1=0,此时hash中存在以0为键的键值对,键值为-1。此时索i=1,ans=1-(-1)=2。
- sum+=nums[2]=0-1=-1,此时hash中存在以-1为键的键值对,键值为1,此时索引i=2,ans=2-0=2。
第三步:返回结果
此时已经遍历完数组,ans=2,返回即可。
矩阵区域和

算法分析
本道题看起来难理解,其实就是那就通过下图来进行理解。

不难看出,其实题目要计算的,其实就是要计算向外扩大k个方格的矩阵的和并将计算出以每个点为中心的矩阵和以一个新的矩阵返回。
算法步骤
- 初始化:定义n并初始化为mat数组的行数,定义m并初始化为mat[0].length,即列的长度。创建一个(n+1)*(m+1)的二维前缀和数组dp。
- 预处理dp数组:在上一篇我们已经讲过如何实现一个dp数组,这里我直接上式子:dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mat[i-1][j-1],为什么是mat[i-1][j-1],在前面我们的二维数组是从1下标开始的,但这里是从0开始,所以dp[i][j]中对应mat中的mat[i-1][j-1].
- 定义ret结果矩阵:这里我们定义个大小为(n*m)的ret二维数组,用来存放矩阵和。
- 定义坐标:这里我们需要定义矩阵的左上角坐标(x1,y1),以及右下角坐标(x2,y2).在计算坐标时,由于dp是从1开始,而ret是从0开始的,所以我们在dp中取值时,坐标都要+1,同时需要考虑当前索引下的i-k和i-j是否越界。依据上述:x1=Math.max(0,i-k)+1 y1=Math.max(0,j-k)+1; x2=Math.min(n-1,i+k)+1 y2=Math.min(m-1,j+k)+1.
- 计算结果:当完成坐标的计算,那么我们就可以根据ret[i][j]=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1]计算矩阵和
- 返回ret矩阵:当完成计算之后,我们返回ret矩阵即可。
算法代码
/*** 计算矩阵中每个元素的块和** @param mat 输入的矩阵* @param k 块的大小参数* @return 每个元素的块和构成的新矩阵*/public int[][] matrixBlockSum(int[][] mat, int k) {// 获取矩阵的行数int n = mat.length;// 获取矩阵的列数int m = mat[0].length;// 初始化二维前缀和数组,大小为矩阵大小加1,用于方便计算int[][] dp = new int[n + 1][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] + mat[i - 1][j - 1];}}// 初始化结果矩阵,用于存放每个元素的块和int[][] ret = new int[n][m];// 遍历矩阵,计算每个元素的块和for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {// 计算块的左上角坐标int x1 = Math.max(0, i - k)+1;int y1 = Math.max(0, j - k)+1;// 计算块的右下角坐标int x2 = Math.min(n - 1, i + k)+1;int y2 = Math.min(m - 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;}
时空复杂度为O(n*m),n*m是数组的大小,整个过程需要开辟两个数组。
算法示例
以mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1为例
这里不做太多说明,上图!

以上就是前缀和算法的专题训练,就先到这里了~
若有不足,欢迎指正~
相关文章:
【Java算法专场】前缀和(下)
目录 和为 K 的子数组 算法分析 算法步骤 算法代码 算法示例 和可被 K 整除的子数组 算法分析 同余定理 负数取余 算法步骤 算法代码 算法示例 连续数组 算法分析 算法步骤 算法代码 算法示例 矩阵区域和 算法分析 算法步骤 算法代码 算法示例 算法分析 …...
音视频相关文章总目录
为了方便各位观看,本文置顶,以目录形式汇集我写过的大部分音视频专题文章。之后文章更新,本目录也会同步更新。写得不好和零零散散的文章就不放在这里了😅 : 音视频入门基础:像素格式专题系列文章&#x…...
7月31日MySQL学习笔记
今日内容: mysql: 行列转换 数据类型 函数 触发器 存储过程 事务 索引(还没讲) 三范式 JDBC连接数据库的6个步骤 三握四挥 行列转换 第一步 新建要转换的列 select name, 1 as 语文, 1 as 数学, 1 as 英语 from t_score GROUP BY name 第二步 对每一列填入值…...
什么是容器查询?分享 1 段优质 CSS 代码片段!
本内容首发于工粽号:程序员大澈,每日分享一段优质代码片段,欢迎关注和投稿! 大家好,我是大澈! 本文约 700 字,整篇阅读约需 1 分钟。 今天分享一段优质 CSS 代码片段,使用容器查询…...
【linux深入剖析】初识线程---线程概念
🍁你好,我是 RO-BERRY 📗 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 目录 1. Linux线程概念什么是线…...
【MySQL】索引——索引的引入、认识磁盘、磁盘的组成、扇区、磁盘访问、磁盘和MySQL交互、索引的概念
文章目录 MySQL1. 索引的引入2. 认识磁盘2.1 磁盘的组成2.2 扇区2.3 磁盘访问 3. 磁盘和MySQL交互4. 索引的概念4.1 索引测试4.2 Page4.3 单页和多页情况 MySQL 1. 索引的引入 海量表在进行普通查询的时候,效率会非常的慢,但是索引可以解决这个问题。 -…...
python部署flask项目
python部署flask项目 1. 准备服务器2. 设置服务器环境3. 创建虚拟环境并安装项目依赖4. 配置Gunicorn5. 配置Nginx6. 设置Supervisor(可选)7. 测试部署 将Flask项目部署到服务器的流程大致如下: 1. 准备服务器 首先,需要准备一台…...
数据建模标准-基于事实建模
前情提要 数据模型定义 DAMA数据治理体系中将数据模型定义为一种文档形式,数据模型是用来将数据需求从业务传递到IT,以及在IT内部从分析师、建模师和架构师到数据库设计人员和开发人员的主要媒介; 作用 记录数据需求和建模过程中产生的数据定义&…...
量产部落SM2258XT开卡软件,SM2258XT主控128G SSD固态卡死修复
故障现象:连接此固态硬盘后电脑就会卡死,拔掉重新连接概率性显示盘符,显示了之后也不能正常操作,一点击打开,电脑就立马卡死。 解决过程:下载了很多款量产工具,都不能开卡成功,点击…...
《零散知识点 · 自定义 HandleMapping》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...
谈谈我对微服务的理解2.0
文章目录 一、引出问题二、微服务2-1、微服务的技术2-2、微服务的目的 三、微服务的拆分四、不连表查询五、微服务的好处六、微服务的坏处七、应付当下 这篇文章原本叫《如何做到不连表查询》,因为我对这个事一直耿耿于怀。在上家公司我经常被连表折磨(连…...
ECCV 2024前沿科技速递:GLARE-基于生成潜在特征的码本检索点亮低光世界,低光环境也能拍出明亮大片!
在计算机视觉与图像处理领域,低光照条件下的图像增强一直是一个极具挑战性的难题。暗淡的光线不仅限制了图像的细节表现,还常常引入噪声和失真,极大地影响了图像的质量和可用性。然而,随着ECCV 2024(欧洲计算机视觉会议…...
前端低代码必备:FrontendBlocks 4.0版本重磅发布,助力Uniapp-X原生APP开发
项目介绍 本软件是一款强大的所见即所得前端页面设计器,是低代码开发领域的基础设施,生成的代码不依赖于任何框架,实测可以将前端布局工作的耗时减少80%以上,最关键的是,它实现了人人都可以写前端页面的梦想。 不用写…...
如何将PyCharm 中使用 PDM 管理的 Django 项目迁移到 VS Code 并确保一切正常工作?
嗨,我是兰若姐姐,相信很多小伙伴都遇到过这种情况,使用pycharm用习惯了,想换个编辑器,比如换成vscode,今天就告诉大家,如果轻松切换到vscode 步骤 1:在 VS Code 中打开项目 打开 V…...
认识Android Handler
“Android Handler” 通常指的是 Android 开发中的 Handler 类,它是 Android SDK 的一部分,用于管理消息队列和线程之间的通信。它在 Android 开发中非常有用,特别是在计划消息和可运行对象(Runnables)在未来某个时间点…...
如何在 Ubuntu VPS 上安装 Cassandra 并运行单节点集群
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 介绍 Cassandra,或者说 Apache Cassandra,是一个高度可扩展的开源数据库系统,在多节点设置上能够实…...
Golang | Leetcode Golang题解之第316题去除重复字母
题目: 题解: func removeDuplicateLetters(s string) string {left : [26]int{}for _, ch : range s {left[ch-a]}stack : []byte{}inStack : [26]bool{}for i : range s {ch : s[i]if !inStack[ch-a] {for len(stack) > 0 && ch < stack…...
pxe的实验
首先搭好实验环境、 如果没有安装好图形,则需要用yum groups list找到有“GUI”的然后用yum groups " " 把含有GUI的复制到双引号里安装 然后再执行init 5 打开图形 Kickstart 如果dnf用不了改成yum 然后在用yum install httpd -y 安装好http的软件 之后…...
复杂智能软件系统开发
软件开发技术总是伴随着计算技术的时代问题向前发展,随着智能计算时代的到来,软件界需要回应智能软件开发的问题。 大型机时代,软件开发的主要问题是软件开发的效率和质量问题,用机器指令或汇编语言编写软件,效率低、质量差。随着高级程序设计语言的出现及其自动编译技术…...
kickstart自动安装脚本
当安装Linux操作系统时,安装过程会需要回答很多关于设定的问题 这些问题必须手动选择,否则无法进行安装。当只安装1台Linux系统,手动选择设定工作量比较轻松,当安装多台Linux,这些设定需要重复多次,这些重复…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...
