算法-前缀和与差分
一、前缀和(Prefix Sum)
1. 核心思想
前缀和是一种预处理数组的方法,通过预先计算并存储数组的前缀和,使得后续的区间和查询可以在**O(1)**时间内完成。
2. 定义
给定数组 nums,前缀和数组 prefixSum 的每个元素 prefixSum[i] 表示从 nums[0] 到 nums[i] 的和:
prefixSum[i] = nums[0] + nums[1] + ... + nums[i]
3. 代码实现
// 构建前缀和数组
public int[] buildPrefixSum(int[] nums) {int n = nums.length;int[] prefixSum = new int[n];prefixSum[0] = nums[0];for (int i = 1; i < n; i++) {prefixSum[i] = prefixSum[i - 1] + nums[i];}return prefixSum;
}// 查询区间和 [left, right]
public int queryRangeSum(int[] prefixSum, int left, int right) {if (left == 0) return prefixSum[right];return prefixSum[right] - prefixSum[left - 1];
}
4. 应用场景
-
高频区间和查询:例如多次查询数组的某个子区间和。
-
二维扩展:处理二维矩阵中的子矩阵和(如LeetCode 304题)。
5. 示例
假设 nums = [1, 2, 3, 4],前缀和数组为 [1, 3, 6, 10]:
-
queryRangeSum([0, 2])→6(即1+2+3) -
queryRangeSum([1, 3])→9(即2+3+4)
前缀和是从1开始的 定义S[0]=0。注意区间和M-N之间的和是S[N]-S[M-1]
一维前缀和
- 定义 对于给定的数列
,其前缀和数列
定义为
,并且规定
。例如数列
,
,
,
,
。 - 计算方法 可以通过递推方式计算前缀和,即
。在 Java 代码中实现如下:
import java.util.Scanner;public class OneDPrefixSum {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int m = scanner.nextInt();int[] a = new int[n + 1];int[] s = new int[n + 1];for (int i = 1; i <= n; i++) {a[i] = scanner.nextInt();}for (int i = 1; i <= n; i++) {s[i] = s[i - 1] + a[i];}for (int i = 0; i < m; i++) {int l = scanner.nextInt();int r = scanner.nextInt();System.out.println(s[r] - s[l - 1]);}}
}
这里首先通过循环读取数组 a 的元素,然后计算前缀和数组 s 。对于每次询问的区间 [l, r],其区间和通过
快速得到。原理是
是前 r 项的和,
是前 l - 1 项的和,两者相减就得到区间 [l, r]的和。
3. 应用场景 当需要频繁查询数列中某一区间的和时,使用前缀和可将每次查询时间复杂度从 O(n)(暴力求和)降低到O(1) 。比如统计一段时间内数据的累计和等场景。
二维前缀和详解
二维前缀和是一种用于快速计算矩阵中子矩阵元素和的高效算法,适用于需要频繁查询子矩阵和的场景(如LeetCode 304题)。
一、核心思想
通过预处理构建一个前缀和矩阵,使得任意子矩阵的和可以在 O(1) 时间内查询。
核心公式(假设矩阵从左上角 (0,0) 开始):
prefixSum[i][j]=matrix[i][j]+prefixSum[i−1][j]+prefixSum[i][j−1]−prefixSum[i−1][j−1]prefixSum[i][j]=matrix[i][j]+prefixSum[i−1][j]+prefixSum[i][j−1]−prefixSum[i−1][j−1]
查询子矩阵 (x1, y1) 到 (x2, y2) 的和:
sum=prefixSum[x2][y2]−prefixSum[x1−1][y2]−prefixSum[x2][y1−1]+prefixSum[x1−1][y1−1]sum=prefixSum[x2][y2]−prefixSum[x1−1][y2]−prefixSum[x2][y1−1]+prefixSum[x1−1][y1−1]
二、实现步骤
1. 构建二维前缀和数组
假设原始矩阵为 matrix[m][n],前缀和矩阵为 prefixSum[m][n]:
public int[][] buildPrefixSum(int[][] matrix) {int m = matrix.length, n = matrix[0].length;int[][] prefixSum = new int[m][n];prefixSum[0][0] = matrix[0][0];// 初始化第一行和第一列for (int j = 1; j < n; j++) prefixSum[0][j] = prefixSum[0][j-1] + matrix[0][j];for (int i = 1; i < m; i++) prefixSum[i][0] = prefixSum[i-1][0] + matrix[i][0];// 填充其他位置for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {prefixSum[i][j] = matrix[i][j] + prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1];}}return prefixSum;
}
2. 查询子矩阵和
public int querySubmatrix(int[][] prefixSum, int x1, int y1, int x2, int y2) {int sum = prefixSum[x2][y2];if (x1 > 0) sum -= prefixSum[x1-1][y2]; // 减去上方区域if (y1 > 0) sum -= prefixSum[x2][y1-1]; // 减去左侧区域if (x1 > 0 && y1 > 0) sum += prefixSum[x1-1][y1-1]; // 补回重复减去的左上角return sum;
}
三、示例演示
假设原始矩阵为:
matrix=[123456789]matrix=147258369
构建前缀和矩阵 prefixSum:
[13651221122745]15123122762145
查询子矩阵 (1,1) 到 (2,2) 的和:
sum=45−21−12+3=15(即 5+6+8+9=28,原矩阵此处有误,实际应为正确计算)sum=45−21−12+3=15(即 5+6+8+9=28,原矩阵此处有误,实际应为正确计算)
四、应用场景
-
高频子矩阵和查询(如 LeetCode 304. 二维区域和检索)。
-
图像处理:计算图像局部区域的像素和。
-
动态规划优化:在二维动态规划问题中减少重复计算。
五、复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 构建前缀和矩阵 | O(mn) | O(mn) |
| 查询子矩阵和 | O(1) | O(1) |
六、注意事项
-
索引边界:矩阵的行列索引需从
0开始,避免越界。 -
空矩阵处理:输入矩阵为空时需返回异常或合理值。
-
大数溢出:若元素值较大,前缀和可能溢出,需使用
long类型存储。
七、完整代码示例
public class TwoDPrefixSum {private int[][] prefixSum;public TwoDPrefixSum(int[][] matrix) {if (matrix.length == 0 || matrix[0].length == 0) return;int m = matrix.length, n = matrix[0].length;prefixSum = new int[m][n];// 构建前缀和矩阵prefixSum[0][0] = matrix[0][0];for (int j = 1; j < n; j++) prefixSum[0][j] = prefixSum[0][j-1] + matrix[0][j];for (int i = 1; i < m; i++) prefixSum[i][0] = prefixSum[i-1][0] + matrix[i][0];for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {prefixSum[i][j] = matrix[i][j] + prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1];}}}public int sumRegion(int x1, int y1, int x2, int y2) {int sum = prefixSum[x2][y2];if (x1 > 0) sum -= prefixSum[x1-1][y2];if (y1 > 0) sum -= prefixSum[x2][y1-1];if (x1 > 0 && y1 > 0) sum += prefixSum[x1-1][y1-1];return sum;}public static void main(String[] args) {int[][] matrix = {{1, 2, 3},{4, 5, 6},{7, 8, 9}};TwoDPrefixSum solver = new TwoDPrefixSum(matrix);System.out.println(solver.sumRegion(1, 1, 2, 2)); // 输出28(5+6+8+9)}
}
通过掌握二维前缀和,你可以高效解决涉及子矩阵和的复杂问题,显著提升算法性能。
差分算法详解
差分(Difference Array)是一种用于高效处理数组区间增减操作的算法技巧,尤其适用于需要多次对数组的某个区间进行增减操作的场景(如 LeetCode 的「航班预订统计」「拼车」问题)。以下是差分算法的完整解析:
一、核心思想
-
差分数组定义
给定原数组nums,其差分数组diff满足:-
diff[0] = nums[0] -
diff[i] = nums[i] - nums[i-1](当i > 0时)
-
-
区间操作优化
对原数组的区间[left, right]进行增减操作时,只需修改差分数组的两个位置:-
diff[left] += val -
若
right + 1 < n,则diff[right + 1] -= val
最后通过差分数组还原原数组时,区间增减的效果会自动生效。
-
二、实现步骤
1. 构建差分数组
// 输入原数组 nums,返回差分数组 diff
public int[] buildDiffArray(int[] nums) {if (nums == null || nums.length == 0) return new int[0];int n = nums.length;int[] diff = new int[n];diff[0] = nums[0];for (int i = 1; i < n; i++) {diff[i] = nums[i] - nums[i - 1];}return diff;
}
2. 区间增减操作
// 对原数组的区间 [left, right] 增加 val(通过差分数组间接实现)
public void rangeUpdate(int[] diff, int left, int right, int val) {diff[left] += val;if (right + 1 < diff.length) {diff[right + 1] -= val;}
}
3. 通过差分数组还原原数组
// 输入差分数组 diff,返回还原后的原数组 nums
public int[] restoreArray(int[] diff) {if (diff == null || diff.length == 0) return new int[0];int n = diff.length;int[] nums = new int[n];nums[0] = diff[0];for (int i = 1; i < n; i++) {nums[i] = nums[i - 1] + diff[i];}return nums;
}
三、示例演示
假设原数组 nums = [1, 3, 5, 7],其差分数组为 diff = [1, 2, 2, 2]:
-
对区间
[1, 2]增加 3:rangeUpdate(diff, 1, 2, 3); // diff 变为 [1, 5, 2, -1] -
还原后的数组:
restoreArray(diff); // 结果 [1, 6, 8, 7]验证:原数组的
[1, 2]区间(即3, 5)被增加了 3,结果变为6, 8。
四、应用场景
-
高频区间更新
例如多次对数组的某个区间进行增减操作,时间复杂度从 O(kn) 优化到 O(n + k),其中 k 是操作次数 -
动态维护数组状态
例如游戏中多个区域同时施加增益/减益效果。
五、复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 构建差分数组 | O(n) | O(n) |
| 单次区间更新 | O(1) | O(1) |
| 还原原数组 | O(n) | O(n) |
六、完整代码示例
public class DifferenceArray {private int[] diff;// 根据原数组构建差分数组public DifferenceArray(int[] nums) {if (nums.length == 0) return;int n = nums.length;diff = new int[n];diff[0] = nums[0];for (int i = 1; i < n; i++) {diff[i] = nums[i] - nums[i - 1];}}// 区间 [left, right] 增加 valpublic void rangeUpdate(int left, int right, int val) {diff[left] += val;if (right + 1 < diff.length) {diff[right + 1] -= val;}}// 还原原数组public int[] restore() {int n = diff.length;int[] nums = new int[n];nums[0] = diff[0];for (int i = 1; i < n; i++) {nums[i] = nums[i - 1] + diff[i];}return nums;}// 测试代码public static void main(String[] args) {int[] nums = {1, 3, 5, 7};DifferenceArray da = new DifferenceArray(nums);da.rangeUpdate(1, 2, 3); // 对区间 [1,2] 增加3int[] result = da.restore();System.out.println(Arrays.toString(result)); // 输出 [1, 6, 8, 7]}
}
七、注意事项
-
索引边界:注意
right + 1是否超出数组范围(避免越界)。 -
初始数组处理:原数组为空时需特殊处理。
-
多次更新:可连续调用
rangeUpdate,最后统一还原数组。
通过差分算法,可以将多次区间操作的时间复杂度从 O(kn) 优化到 O(n + k),是处理大规模区间更新问题的核心技巧。
相关文章:
算法-前缀和与差分
一、前缀和(Prefix Sum) 1. 核心思想 前缀和是一种预处理数组的方法,通过预先计算并存储数组的前缀和,使得后续的区间和查询可以在**O(1)**时间内完成。 2. 定义 给定数组 nums,前缀和数组 prefixSum 的每个元素 p…...
React(六)React过渡动画-CSS编写方式
React过渡动画 react-transition-group介绍 在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,提高用户体验→可通过react-transition-group实现。React曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护…...
第十五章:Python的Pandas库详解及常见用法
在数据分析领域,Python的Pandas库是一个不可或缺的工具。它提供了高效的数据结构和数据分析工具,使得数据处理变得简单而直观。本文将详细介绍Pandas库的基本功能、常见用法,并通过示例代码演示如何使用Pandas进行数据处理。最后,…...
Python自动化模块:开启高效编程新时代
一、写在前面 在数字化时代,自动化技术已成为提高效率、降低成本的关键手段。Python 作为一种简洁、高效且功能强大的编程语言,凭借其丰富的库和框架,在自动化领域占据了举足轻重的地位,成为众多开发者的首选工具之一。从简单的文…...
【蓝桥杯速成】| 15.完全背包
题目:携带研究材料 问题描述 52. 携带研究材料(第七期模拟笔试) 小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研…...
C++:allocator类(动态数组续)
1.为什么需要 allocator? 在 C 中,动态内存管理通常通过 new 和 delete 完成: int* p new int; // 分配内存 构造对象 delete p; // 析构对象 释放内存 但 new 和 delete 有两个问题: 耦合性:将内…...
libva基础
Libva(Lib Video Acceleration)是一个开源的库,实现了 **VA-API**(Video Acceleration API),旨在为视频处理提供跨平台的硬件加速支持。 1、核心功能与作用 硬件加速抽象层:Libva 作为中间层&…...
【C++20】format格式化输出
C20 format格式化输出 在C20之前,格式化能力都依赖于三方格式化库FMT, 而C20 标准委员会终于在C标准库引入了格式化功能,从使用方式和风格来看其实就是FMT库转正了 直接使用 包含<format.h>头文件既可以直接使用,类似pyt…...
c++游戏开发第一期
以后我将要发c游戏开发的教程,可能更得比较慢。(目测几个星期一更)。 今天先讲个配置编译器。 我用的是Visual studio 2022和EasyX。 安装studio: 首先找到下载链接(点我)下拉找到下面图片的东西。 下完…...
Elasticsearch:人工智能时代的公共部门数据治理
作者:来自 Elastic Darren Meiss 人工智能(AI)和生成式人工智能(GenAI)正在迅速改变公共部门,从理论探讨走向实际应用。正确的数据准备、管理和治理将在 GenAI 的成功实施中发挥关键作用。 我们最近举办了…...
Web开发:数据的加密和解密
一、常见通用术语解析 加盐:在密码中加入随机数据,提高安全性。摘要:固定长度的输出,用于数据完整性验证。加密:将数据转换为不可读形式,确保安全。撞库:通过暴力破解比对常见密码的攻击方式。…...
低功耗LPWAN模块开发指南:远距离无线通信与边缘计算融合实战
在远程资产追踪、野外环境监测等场景中,稳定可靠的长距离通信与超低功耗是系统设计的核心挑战。eFish-SBC-RK3576通过 原生双UART接口 USB OTG扩展能力 ,可无缝集成主流LPWAN模组(LoRa/NB-IoT),实现“数据采集-边…...
RHCA核心课程技术解析5:红帽高可用性集群架构与深度实践
一、红帽高可用集群架构全景 1.1 核心组件交互逻辑 graph TD A[节点1] -->|Corosync 心跳| B[节点2] A -->|Pacemaker 资源管理| C[共享存储] B --> C D[Fencing设备] -->|STONITH| A D -->|STONITH| B C -->|GFS2锁管理| A C -->|GFS2锁管理| B 1.2 集…...
Python切片中的步长秘密
Python切片中的步长秘密 大家好!今天我们来聊聊Python切片中一个有趣的话题 - 步长(step)。 基本格式回顾 Python切片的完整格式是: [起点:终点:步长] 但你是否注意到,很多代码里的切片都只写了起点和终点?没错,步长是可以省略的! 步长的默认…...
Spring Boot事务管理详解(附银行转账案例)
一、事务基础概念 事务的ACID特性: 原子性(Atomicity):操作要么全部成功,要么全部失败一致性(Consistency):数据在事务前后保持合法状态隔离性(Isolation)&…...
【超详细教程】2025年3月最新Pytorch安装教程(同时讲解安装CPU和GPU版本)
目录 一、前言二、pytorch简介三、安装准备工作3.1、下载Anaconda 四、判断是否有NVIDIA显卡五、安装pytorch-CPU版本六、安装pytorch-GPU版本6.1、查看CUDA显卡驱动版本6.2、安装CUDA6.3、安装CuDNN(加速器)6.4、安装pytorch-GPU6.5 其他方法安装注意 七…...
Unity光线传播体积(LPV)技术实现详解
一、LPV技术概述 光线传播体积(Light Propagation Volumes)是一种实时全局光照技术,通过将场景中的间接光信息存储在3D网格中,实现动态物体的间接光照效果。 核心优势: 实时性能:相比传统光照贴图,支持动态场景 硬件…...
Git和GitCode使用(从Git安装到上传项目一条龙)
第一步 菜鸟教程-Git教程 点击上方链接,完成Git的安装,并了解Git 工作流程,知道Git 工作区、暂存区和版本库的区别 第二步 GitCode官方帮助文档-SSH 公钥管理 点击上方链接,完成SSH公钥设置 第三步(GitCode的官方引…...
通信之光纤耦合器
以下是关于光纤耦合器的详细介绍: 定义与原理 - 定义:光纤耦合器是一种能使传输中的光信号在特殊结构的耦合区发生耦合,并进行再分配的器件,也叫分歧器、连接器、适配器、光纤法兰盘。 - 原理:利用不同光纤面紧邻光纤芯…...
5G核心网(5GC)开户中,DNN(Data Network Name,数据网络名称)
在5G核心网(5GC)开户中,DNN(Data Network Name,数据网络名称)是关键概念之一,以下是关于它的详细介绍: 定义 DNN是5G网络中用于标识外部数据网络的名称,相当于4G中的APN(Access Point Name),两者功能等价。 组成 DNN由两部分组成: 网络ID(NI):必选,至少包…...
OpenCV、YOLO与大模型的区别与关系
OpenCV、YOLO 和大模型的区别与关系 1. OpenCV(Open Source Computer Vision Library) 定位:开源的计算机视觉基础库。功能:提供传统的图像处理算法(如图像滤波、边缘检测、特征提取)和基础工具ÿ…...
虚拟电商-话费充值业务(二)话费充值对接供应商模块开发
一、对接供应商模块开发 供应商对接模块chongba_recharge_supplier主要负责的就是调用外部的供应商系统进行充值下单,这种调用是一种基于HTTP协议的调用。 此外在供应商对接模块中主要是实现的业务逻辑有: 1:余额或押金不足情况下的失败轮…...
练习题:110
目录 Python题目 题目 题目分析 需求理解 关键知识点 实现思路分析 代码实现 代码解释 函数定义: 计算值的总和: 测试函数: 运行思路 结束语 Python题目 题目 定义一个函数,接受一个字典作为参数,返回字…...
c#winform,倒鸭子字幕效果,typemonkey字幕效果,抖音瀑布流字幕效果
不废话 直接上效果图 C# winform 开发抖音的瀑布流字幕。 也是typemonkey插件字幕效果 或者咱再网上常说的倒鸭子字幕效果 主要功能 1,软件可以自定义添加字幕内容 2,软件可以添加字幕显示的时间区间 3,可以自定义字幕颜色,可以随…...
游戏被外挂攻破?金融数据遭篡改?AI反作弊系统实战方案(代码+详细步骤)
一、背景与需求分析 随着游戏行业与金融领域的数字化进程加速,作弊行为(如游戏外挂、金融数据篡改)日益复杂化。传统基于规则的防御手段已难以应对新型攻击,而AI技术通过动态行为分析、异常检测等能力,为安全领域提供了革命性解决方案。本文以游戏反作弊系统和金融数据安…...
晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包
晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包 适用型号:M401A、CM311-1a、CM311-1sa、B863AV3.1-M2、B863AV3.2-M、UNT403A、UNT413A、M411A、E900V22C、E900V22D、IP112H等等晶晨S905L3A(B)处…...
AI来了,新手如何着手学习软件开发?
AI时代新手学习软件开发的7步进化指南 (附具体工具与避坑策略) 一、建立“人机协作”学习观 AI是教练,不是替身 正确姿势:用AI辅助理解概念(如让DeepSeek 、ChatGPT用生活案例解释递归),但坚持手…...
JDK 24 Class File API 介绍
概述 JDK 24 引入的 Class File API 提供了一套类型安全的 API 用于操作 Java 类文件。这套 API 允许我们以编程方式读取、修改和创建 Java 类文件,而不需要直接处理底层的字节码。 注1:JDK 24 已于2025年3月18日正式发布,Release信息参见官…...
C++23:现代C++的模块化革命与零成本抽象新高度
以下代码为伪代码,仅供参考 一、标准库的范式突破 1. std::expected:类型安全的错误处理 std::expected<DataPacket, ErrorCode> parsePacket(ByteStream& stream) {if (stream.header_valid()) return decode_packet(stream);elsereturn s…...
《K230 从熟悉到...》矩形检测
《K230 从熟悉到...》矩形检测 《庐山派 K230 从熟悉到...》矩形检测 矩形检测技术是一种广泛应用于电子图像处理的核心技术。它通过识别和分析图像中的矩形结构,为各种应用提供基础支持。从传统图像处理算法到现代深度学习技术,矩形检测的实现途径多种多…...
