【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,这些设定需要重复多次,这些重复…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...