【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,这些设定需要重复多次,这些重复…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
