LeetCode-数组-前缀和-中等难度
前缀和
前缀和是一种利用预处理的方式来减少整体实现复杂度的方法。
基本定理
假设原数列A为:[1,2,3,4,5]
,与之对应的前缀和数列P则为:[1,3,6,10,15]
前缀和数列的第一项等于原数列的第一项,从第二项开始前缀和数列每一项计算方法为:P[i] = P[i-1]+A[i]
原数列A与前缀和数列P则有以下几种关系:
- 前缀和数列从第二项起,每一项
(i)
与它的前一项(i - 1)
的差等于原数列(i)
的值。 - 前缀和数列每一项
(i)
等于原数列中(0 ~ i)
项之和。 - 在满足
0 < i < j
时,原数列的第(i ~ j)
项之和等于前缀和数列的第(j)
项减去第(i - 1)
项。
根据这样的关系,在某些应用场景上我们就可以利用前缀和进行快速求解。
1. 区域和检索 - 数组不可变
题目描述
解题思路
根据前面定理中的第三条,可以发现利用前缀和可以快速实现这个需求。
代码实现
这里为了方便计算,额外忽视了前缀和数列的第一位,比如数列为:{1,2,3,4,5}
,对应的前缀和数列为:{0,1,3,6,10,15}
,其中第一位数值0
,没有任何含义,只是为了方便处理。
class NumArray {private int[] preSum;public NumArray(int[] nums) {preSum = new int[nums.length + 1];for(int i = 0; i < nums.length; i++){preSum[i + 1] = preSum[i] + nums[i];}}public int sumRange(int left, int right) {// 本身根据定理应该是p[right] - p[left - 1],// 但由于我们将前缀和数列整体右移了一位,因此就变成了p[right + 1] - p[left],// 这样做的好处是,不用单独处理当left为0的情况了。return preSum[right + 1] - preSum[left];}
}
2. 除自身以外数组的乘积
题目描述
解题思路
本题可以考虑从后向前遍历,那么每一项之前的元素乘积,如果用前缀和思路来处理的话,就是前面定理中的第二条。(符合前缀和的也符合前缀乘积)
比如原数列:{1,2,3,4}
,前缀积数列:{1,2,6,24}
,则遍历原数列最后一位4
时,对应的之前所有数的乘积就是前缀积数列的倒数第二位6
。
现在已经搞定了除目标元素外的左边所有元素的乘积,只要再计算出其右边所有元素的乘积即可。
依据同样的思想,你也可以做一个后缀积来方便直接获取,但就当前这个场景来说倒也没必要,对于右边元素我们只需要直接动态维护即可。
代码实现
class Solution {public int[] productExceptSelf(int[] nums) {int n = nums.length;int[] preSum = new int[n + 1];// 前缀数列第一位无实际意思,仅仅为了方便处理preSum[0] = 1;for (int i = 1; i < n + 1; i++) {preSum[i] = preSum[i - 1] * nums[i - 1];}// 右边元素动态维护int rightSum = 1;int[] ans = new int[n];for (int i = n - 1; i >= 0; i--) {ans[i] = preSum[i] * rightSum;rightSum *= nums[i];}return ans;}
}
3. 和相同的二元子数组
题目描述
解题思路
假设有:原数列1 0 1 0 1
,和与其对应的前缀和数列:1 1 2 2 3
。
- 我们知道子数组可以看作
[left,right]
这样一个区间,并且left
和right
满足:0 <= left <= right
. - 所以本题可以看作是在要求
0 <= left <= right
这样一个区间内的所有元素和等于goal
。 - 我们看到求解
0 <= left <= right
这样一个区间之和,就可以思考一下是否可以利用前缀和的性质。很明显在前缀和数列中只需要通过计算right - (left - 1)
,即可得到原数列的0 <= left <= right
区间之和。 - 经过上面3步分析,最终就变成了求
right - (left - 1)
等于goal
的个数。
所以最终我们只需要构造一个前缀和数列,然后通过嵌套循环的方式,以每个right
为子数组的结束区间,挨个检查子数组的开始区间即可。
public int numSubarraysWithSum(int[] nums, int goal) {int[] perSum = new int[nums.length + 1];for (int i = 0; i < nums.length; i++) {perSum[i + 1] = perSum[i] + nums[i];}int ans = 0;for (int right = 1; right < perSum.length; right++) {for (int left = right; left > 0; left--) {if (perSum[right] - perSum[left - 1] == goal) {ans++;}}}return ans;
}
复杂度优化
由于存在嵌套循环,因此复杂度较高,不难发现,每次内层循环中只是在计算在0~right-1
下标对应的数值中有多少个等于perSum[right] - goal
,因此我们考虑将每一个0~right-1
对应的结果直接记录下来,这样不就可以省去内层循环的处理了吗~
public int numSubarraysWithSum(int[] nums, int goal) {int[] perSum = new int[nums.length + 1];for (int i = 0; i < nums.length; i++) {perSum[i + 1] = perSum[i] + nums[i];}int ans = 0;// key表示前缀和数列坐标对应的值,value表示改值出现的次数Map<Integer, Integer> map = new HashMap<>();// 前缀和第一位坐标0需特殊处理一下,对应的值为0,出现1次。map.put(0, 1);for (int right = 1; right < perSum.length; right++) {if (map.containsKey(perSum[right] - goal)) {ans += map.get(perSum[right] - goal);}map.put(perSum[right], map.getOrDefault(perSum[right], 0) + 1);}return ans;
}
如果还想简化一点,则可以动态维护前缀和数组,如下:
public int numSubarraysWithSum(int[] nums, int goal) {int ans = 0;Map<Integer, Integer> map = new HashMap<>();map.put(0, 1);int perSum = 0;for (int right = 0; right < nums.length; right++) {perSum += nums[right];if (map.containsKey(perSum - goal)) {ans += map.get(perSum - goal);}map.put(perSum, map.getOrDefault(perSum, 0) + 1);}return ans;
}
类似题型
与之类似的题目还有:
560.和为K的子数组
974.和可被 K 整除的子数组
4. 连续的子数组和
题目描述
解题思路
本题关键在于解决子数组元素总和为K的倍数这个问题。
首先,我们发现是求解子数组的问题,通过前面的练习,我们知道在前缀和数列中,求解子数组的和,等同于求解其前缀和数组的right - (left - 1)
。
同余定理
给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。
因此,如果(right - (left - 1))
是K的整数倍,则根据同余定理则有 right % k == (left - 1) % k
。
有了这个结论以后,接下来的处理方式就和前面差不多了,通过一个哈希表用Key
记录每一项的余数,Value
用来记录下标,为了满足题目中大小至少为2的要求,下标位置不更新。
代码实现
public boolean checkSubarraySum(int[] nums, int k) {int sum = 0;Map<Integer, Integer> map = new HashMap<>();map.put(0, -1);for (int i = 0; i < nums.length; i++) {sum += nums[i];int mod = sum % k;if (map.containsKey(mod)) {if (i - map.get(mod) >= 2) {return true;}} else {map.put(mod, i);}}return false;
}
5. 连续数组
题目描述
解题思路
如果把拥有相同数量的0
和1
理解为互相抵消,假设我们有一个计数器C,当遇到0
就减1
,遇到1
就加1
,因此当C等于0
时,则0
和1
的数量一定相同,所以这个题目就又变成了子数组求和的问题了,那么看到子数组求和就想到用前缀和数列来解。
我们知道子数组可以看作[left,right]
,其所有元素之和,又等于其对应的前缀和数列right - (left - 1)
的计算结果,这个在前面几题中也一直在用,又因为如果right == (left - 1)
,则right - (left - 1)==0
,也就是我们要求的结果,子数组区间内所有元素之和为0
。所以,最后我们要求的其实就是长度最长的right == (left - 1)
。
代码实现
public int findMaxLength(int[] nums) {Map<Integer, Integer> map = new HashMap<>();// 表示0值,出现的下标位置map.put(0, -1);int sum = 0;int ans = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i] == 0 ? -1 : 1;if (map.containsKey(sum)) {ans = Math.max(ans, i - map.get(sum));} else {map.put(sum, i);}}return ans;
}
类似题型
与之类似的题目还有:
字母与数字
6. 和为奇数的子数组数目
题目描述
解题思路
还是求子数组和的问题,要求为奇数,我们知道,两奇数或两偶数相加结果为偶数,只有一奇一偶相加结果才为奇数。
所以,根据right - (left - 1)
等于原数组的子数组之和,可以得出以下结论:
-
当
right
为偶数时,left - 1
必须为奇数,子数组之和才为奇数。 -
当
right
为奇数时,left - 1
必须为偶数,子数组之和才为奇数。
代码实现
public int numOfSubarrays(int[] arr) {int ans = 0;int mod = 1000000007;int odd = 0;int even = 1;int sum = 0;for (int i = 0; i < arr.length; i++) {sum += arr[i];ans = (ans + (sum % 2 == 0 ? odd : even)) % mod;if(sum % 2 == 0){even++;}else{odd++;}}return ans;
}
7. 统计「优美子数组」
题目描述
解题思路
可以把奇数的个数当做前缀和数列来记录,比如对于[1,1,2,1,1]
这样的数列,对应的奇数前缀和数列为[0,1,2,2,3,4]
,每一项表示的是奇数个数(注意:0
个奇数也是有计算含义的)。
那么如果子数组恰好有K个奇数,则有right - (left - 1) = K
,转换一下也就是right - K = left - 1
,这就回到了我们习惯的哈希表处理方式,无需说明,直接看代码。
代码实现
public int numberOfSubarrays(int[] nums, int k) {int ans = 0;int oddCnt = 0;Map<Integer, Integer> map = new HashMap<>();// 初始化map表示:出现0个奇数的次数为1map.put(0, 1);for (int i = 0; i < nums.length; i++) {if (nums[i] % 2 != 0) {oddCnt++;}if (map.containsKey(oddCnt - k)) {ans += map.get(oddCnt - k);}map.put(oddCnt, map.getOrDefault(oddCnt, 0) + 1);}return ans;
}
相关文章:

LeetCode-数组-前缀和-中等难度
前缀和 前缀和是一种利用预处理的方式来减少整体实现复杂度的方法。 基本定理 假设原数列A为:[1,2,3,4,5],与之对应的前缀和数列P则为:[1,3,6,10,15] 前缀和数列的第一项等于原数列的第一项,从第二项开始前缀和数列每一项计算…...
【程序人生】探索2024年AI辅助研发趋势
目录标题 探索2024年AI辅助研发趋势一、AI在编码中的应用智能代码生成助力开发错误检测与修复的即时反馈性能优化的智能建议 二、AI驱动的自动化工具三、AI与团队协作四、未来展望结语 探索2024年AI辅助研发趋势 随着人工智能技术的迅速发展,AI在各个领域的应用正日…...

集合框架(一)Collection
学习过了ArrayList,知道集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用。 为了满足不同的业务场景需求Java还提供了很多不同特点的集合给我们选择。 集合体系结构 Collection是一个接口&a…...

Android 性能优化--APK加固(2)加密
文章目录 字符串加密图片加密如何避免应用被重新签名分发APK 加壳的方案简析DEX加密原理及实现 本文首发地址:https://h89.cn/archives/212.html 最新更新地址:https://gitee.com/chenjim/chenjimblog 通过 前文 介绍,我们知晓了如何使用代码…...

Linux环境下使用interrupt方式操作UART
目录 概述 1 Linux环境下UART设备 2 轮询方式操作UART功能实现 2.1 打开串口函数:usr_serial_open 2.2 关闭串口函数: usr_serial_close 2.3 发送数据函数: usr_serial_sendbytes 2.4 接收数据函数: usr_serial_readinterr…...

修改Android打包apk的名字和目录
app打包生成apk后通常需要进行备份,但是要区分好哪个apk是什么版本的、什么时候打包的,以方便以后区分使用。 最开始的想法是把版本号、创建时间这些加在apk文件名上即可,但是公司要求apk使用一个固定的名称,那我怎么保存版本号信…...

管理 PostgreSQL 中配置参数的各种方法
管理 PostgreSQL 中配置参数的各种方法 1. 概述 PostgreSQL提供了一个配置文件 postgresql.conf 让用户自定义参数。您可能需要更改一些参数来调整性能或在工作环境中部署 PostgreSQL 服务器。在这篇博文中,我们将探索管理这些参数的不同方法。 2. 以不同方式管理…...
Linux命令-continue命令(结束本次循环,继续执行下一个for,while或until循环。)
概要 continue [n]主要用途 结束本次循环,继续执行下一个for,while或until循环;可指定从第几层循环继续执行。 参数 n(可选):大于等于1的整数,用于指定从第几层循环继续执行。 返回值 返回…...

智能部署之巅:Amazon SageMaker 引领机器学习革新
本篇文章授权活动官方亚马逊云科技文章转发、改写权,包括不限于在 亚马逊云科技开发者社区, 知乎,自媒体平台,第三方开发者媒体等亚马逊云科技官方渠道。 (全球 TMT 2023年12月6日讯)亚马逊云科技在 2023 re:Invent 全…...

国内哪个工具可以平替chatgpt?国内有哪些比较好用的大模型gpt?
我自己试用了很多的平台,发现三个比较好的大模型平台,对普通用户也比较的友好的,而且返回内容相对来说,正确率更高的,并且相关场景插件比较丰富的国内厂商。 本文说的,是我自己觉得的,比较有主观…...
python如何打包py文件为exe
要将Python程序打包为可执行文件(.exe),您可以使用一些第三方工具。以下是两个常用的工具:PyInstaller和cx_Freeze。 使用PyInstaller PyInstaller是一个流行的Python打包工具,可以将Python程序及其所有依赖项打包为…...

yolov9网络结构图
文章目录 配置文件主干分支backbone预测头headyolov9网络结构图 系列文章目录 论文链接:👿 YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information代码链接:👿 https://github.com/WongKinYiu/yolov9…...

Spark 核心API
核心 API spark core API 指的是 spark 预定义好的算子。无论是 spark streaming 或者 Spark SQL 都是基于这些最基础的 API 构建起来的。理解这些核心 API 也是写出高效 Spark 代码的基础。 Transformation 转化类的算子是最多的,学会使用这些算子就应付多数的数…...

OpenLayers线性渐变和中心渐变(径向渐变)
目录 1.前言2.添加一个面要素3.线性渐变3.1 第一个注意点3.2 第二个注意点 4.中心渐变(径向渐变)5.总结 1.前言 OpenLayers官网有整个图层的渐变示例,但是没有单个要素的渐变示例,我们这里来补充一下。OpenLayers中的渐变是通过fi…...
[210. 课程表 II] 拓扑排序模板(DFS+BFS)
Problem: 210. 课程表 II 文章目录 思路解题方法Code 思路 本题是经典拓扑排序模板,通过DFS和BFS两种方式进行实现。 解题方法 DFS DFS方法的重点在于如何标记节点状态,初做题者如果只用未访问和已访问两种状态很容易陷入死结。正确的做法是使用三种状…...
我的第一个python web 网站
# -*- coding: utf-8 -*-import http.server import socketserver from datetime import datetimePORT 8000import sys# ...class MyHandler(http.server.SimpleHTTPRequestHandler):def do_GET(self):if self.path /:# 如果路径是根路径,返回页面内容self.send_r…...

产品展示型wordpress外贸网站模板
孕婴产品wordpress外贸网站模板 吸奶器、待产包、孕妇枕头、护理垫、纸尿裤、孕妇装、孕婴产品wordpress外贸网站模板。 https://www.jianzhanpress.com/?p4112 床品毛巾wordpress独立站模板 床单、被套、毛巾、抱枕、靠垫、围巾、布艺、枕头、乳胶枕、四件套、浴巾wordpre…...

四信全球化拓展再启新篇!LoRa传感器与云平台领航智能感知时代
随着科技浪潮的不断推进,物联网已逐渐融入我们的生活。刚刚结束的MWC24盛会上,四信带来了一系列前沿技术成果,不仅将5G技术成功扩展至当前市场主流类型的终端,更携手联通、ASR等业界巨头,在连接、5G RedCap、AI、LoRa以…...

阿里云k8s环境下,因slb限额导致的发布事故
一、背景 阿里云k8s容器,在发布java应用程序的时候,客户端访问出现500错误。 后端服务是健康且可用的,网关层大量500错误请求,slb没有流入和流出流量。 经过回滚,仍未能解决错误。可谓是一次血的教训,特…...
【STM32+OPENMV】矩形识别
一、准备工作 有关OPENMV最大色块追踪及与STM32通信内容,详情见【STM32HAL】与OpenMV通信 二、所用工具 1、芯片:STM32F103C8T6 2、CUBEMX配置软件 3、KEIL5 4、OPENMV 三、实现功能 寻找黑色矩形,并将最大矩形的四个边缘坐标发送给STM…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...