算法-前缀和与差分

news2025/4/3 0:07:50

一、前缀和(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]

一维前缀和

  1. 定义 对于给定的数列,其前缀和数列定义为,并且规定。例如数列 ,
  2. 计算方法 可以通过递推方式计算前缀和,即 。在 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=​147​258​369​​

构建前缀和矩阵 prefixSum

[13651221122745]​1512​31227​62145​​

查询子矩阵 (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,原矩阵此处有误,实际应为正确计算)


四、应用场景
  1. 高频子矩阵和查询(如 LeetCode 304. 二维区域和检索)。

  2. 图像处理:计算图像局部区域的像素和。

  3. 动态规划优化:在二维动态规划问题中减少重复计算。


五、复杂度分析
操作时间复杂度空间复杂度
构建前缀和矩阵O(mn)O(mn)
查询子矩阵和O(1)O(1)

六、注意事项
  1. 索引边界:矩阵的行列索引需从 0 开始,避免越界。

  2. 空矩阵处理:输入矩阵为空时需返回异常或合理值。

  3. 大数溢出:若元素值较大,前缀和可能溢出,需使用 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 的「航班预订统计」「拼车」问题)。以下是差分算法的完整解析:


一、核心思想
  1. 差分数组定义
    给定原数组 nums,其差分数组 diff 满足:

    • diff[0] = nums[0]

    • diff[i] = nums[i] - nums[i-1](当 i > 0 时)

  2. 区间操作优化
    对原数组的区间 [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


四、应用场景
  1. 高频区间更新
    例如多次对数组的某个区间进行增减操作,时间复杂度从 O(kn) 优化到 O(n + k),其中 k 是操作次数

  2. 动态维护数组状态
    例如游戏中多个区域同时施加增益/减益效果。


五、复杂度分析
操作时间复杂度空间复杂度
构建差分数组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] 增加 val
    public 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] 增加3
        int[] result = da.restore();
        System.out.println(Arrays.toString(result)); // 输出 [1, 6, 8, 7]
    }
}

七、注意事项
  1. 索引边界:注意 right + 1 是否超出数组范围(避免越界)。

  2. 初始数组处理:原数组为空时需特殊处理。

  3. 多次更新:可连续调用 rangeUpdate,最后统一还原数组。


通过差分算法,可以将多次区间操作的时间复杂度从 O(kn) 优化到 O(n + k),是处理大规模区间更新问题的核心技巧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2324689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React(六)React过渡动画-CSS编写方式

React过渡动画 react-transition-group介绍 在开发中&#xff0c;我们想要给一个组件的显示和消失添加某种过渡动画&#xff0c;提高用户体验→可通过react-transition-group实现。React曾为开发者提供过动画插件 react-addons-css-transition-group&#xff0c;后由社区维护…

第十五章:Python的Pandas库详解及常见用法

在数据分析领域&#xff0c;Python的Pandas库是一个不可或缺的工具。它提供了高效的数据结构和数据分析工具&#xff0c;使得数据处理变得简单而直观。本文将详细介绍Pandas库的基本功能、常见用法&#xff0c;并通过示例代码演示如何使用Pandas进行数据处理。最后&#xff0c;…

libva基础

Libva&#xff08;Lib Video Acceleration&#xff09;是一个开源的库&#xff0c;实现了 **VA-API**&#xff08;Video Acceleration API&#xff09;&#xff0c;旨在为视频处理提供跨平台的硬件加速支持。 1、核心功能与作用 硬件加速抽象层&#xff1a;Libva 作为中间层&…

c++游戏开发第一期

以后我将要发c游戏开发的教程&#xff0c;可能更得比较慢。&#xff08;目测几个星期一更&#xff09;。 今天先讲个配置编译器。 我用的是Visual studio 2022和EasyX。 安装studio&#xff1a; 首先找到下载链接&#xff08;点我&#xff09;下拉找到下面图片的东西。 下完…

Elasticsearch:人工智能时代的公共部门数据治理

作者&#xff1a;来自 Elastic Darren Meiss 人工智能&#xff08;AI&#xff09;和生成式人工智能&#xff08;GenAI&#xff09;正在迅速改变公共部门&#xff0c;从理论探讨走向实际应用。正确的数据准备、管理和治理将在 GenAI 的成功实施中发挥关键作用。 我们最近举办了…

低功耗LPWAN模块开发指南:远距离无线通信与边缘计算融合实战‌

在远程资产追踪、野外环境监测等场景中&#xff0c;稳定可靠的长距离通信与超低功耗是系统设计的核心挑战。eFish-SBC-RK3576通过 ‌原生双UART接口 USB OTG扩展能力‌ &#xff0c;可无缝集成主流LPWAN模组&#xff08;LoRa/NB-IoT&#xff09;&#xff0c;实现“数据采集-边…

【超详细教程】2025年3月最新Pytorch安装教程(同时讲解安装CPU和GPU版本)

目录 一、前言二、pytorch简介三、安装准备工作3.1、下载Anaconda 四、判断是否有NVIDIA显卡五、安装pytorch-CPU版本六、安装pytorch-GPU版本6.1、查看CUDA显卡驱动版本6.2、安装CUDA6.3、安装CuDNN&#xff08;加速器&#xff09;6.4、安装pytorch-GPU6.5 其他方法安装注意 七…

虚拟电商-话费充值业务(二)话费充值对接供应商模块开发

一、对接供应商模块开发 供应商对接模块chongba_recharge_supplier主要负责的就是调用外部的供应商系统进行充值下单&#xff0c;这种调用是一种基于HTTP协议的调用。 此外在供应商对接模块中主要是实现的业务逻辑有&#xff1a; 1&#xff1a;余额或押金不足情况下的失败轮…

c#winform,倒鸭子字幕效果,typemonkey字幕效果,抖音瀑布流字幕效果

不废话 直接上效果图 C# winform 开发抖音的瀑布流字幕。 也是typemonkey插件字幕效果 或者咱再网上常说的倒鸭子字幕效果 主要功能 1&#xff0c;软件可以自定义添加字幕内容 2&#xff0c;软件可以添加字幕显示的时间区间 3&#xff0c;可以自定义字幕颜色&#xff0c;可以随…

游戏被外挂攻破?金融数据遭篡改?AI反作弊系统实战方案(代码+详细步骤)

一、背景与需求分析 随着游戏行业与金融领域的数字化进程加速,作弊行为(如游戏外挂、金融数据篡改)日益复杂化。传统基于规则的防御手段已难以应对新型攻击,而AI技术通过动态行为分析、异常检测等能力,为安全领域提供了革命性解决方案。本文以游戏反作弊系统和金融数据安…

晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包

晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包 适用型号&#xff1a;M401A、CM311-1a、CM311-1sa、B863AV3.1-M2、B863AV3.2-M、UNT403A、UNT413A、M411A、E900V22C、E900V22D、IP112H等等晶晨S905L3A(B)处…

AI来了,新手如何着手学习软件开发?

AI时代新手学习软件开发的7步进化指南 &#xff08;附具体工具与避坑策略&#xff09; 一、建立“人机协作”学习观 AI是教练&#xff0c;不是替身 正确姿势&#xff1a;用AI辅助理解概念&#xff08;如让DeepSeek 、ChatGPT用生活案例解释递归&#xff09;&#xff0c;但坚持手…

《K230 从熟悉到...》矩形检测

《K230 从熟悉到...》矩形检测 《庐山派 K230 从熟悉到...》矩形检测 矩形检测技术是一种广泛应用于电子图像处理的核心技术。它通过识别和分析图像中的矩形结构&#xff0c;为各种应用提供基础支持。从传统图像处理算法到现代深度学习技术&#xff0c;矩形检测的实现途径多种多…

3. 第三放平台部署deepseek

有时候我们会发现使用deepseek服务器&#xff0c;异常卡顿&#xff0c;这是由于多方面原因造成的&#xff0c;比如说访问人数过多等。想要解决这个问题&#xff0c;我们可以选择第三方平台进行部署 第三方平台 我们可以选择的第三方平台很多&#xff0c;比如硅基流动、秘塔搜索…

【C++指针】搭建起程序与内存深度交互的桥梁(下)

&#x1f525;&#x1f525; 个人主页 点击&#x1f525;&#x1f525; 每文一诗 &#x1f4aa;&#x1f3fc; 往者不可谏&#xff0c;来者犹可追——《论语微子篇》 译文&#xff1a;过去的事情已经无法挽回&#xff0c;未来的岁月还可以迎头赶上。 目录 C内存模型 new与…

IEEE PDF Xpress校验出现 :字体无法嵌入问题以及pdf版本问题

文章目录 问题描述一、字体嵌入问题首先查看一下&#xff0c;哪些字体没有被嵌入查看window的font文件夹里的字体下载字体的网站修复字体嵌入问题 二、pdf版本不对 问题描述 在处理IEEE的camera ready的时候&#xff0c;提交到IEEE express的文件没有办法通过validate&#xf…

cookie详解

一、cookie出现原因 http是无状态的&#xff0c;浏览器无法记录当前是哪个人浏览的&#xff0c;所以出现了cookie 作用&#xff1a;会话状态管理&#xff08;用户登录状态、购物车、游戏分数&#xff09;、个性化设置&#xff08;主题、自定义设置&#xff09;、浏览器行为跟…

Mayo Clinic Platform在人工智能医疗领域的现状及启示意义研究

一、引言 1.1 研究背景与意义 在科技飞速发展的当下,人工智能(AI)已逐渐渗透至各个行业,医疗领域作为关乎人类生命健康的重要领域,也迎来了人工智能技术带来的深刻变革。人工智能医疗,作为人工智能与医疗行业深度融合的产物,正重塑着全球医疗的格局。 从全球范围来看,…

如何将 Java 应用做成 EXE 的可执行软件

目录 前言一、情景介绍二、实现步骤1. 打 Jar 包2. 编写 bat 批处理文件3. bat 转 exe 前言 最近使用 GUI 帮朋友写了一个软件&#xff0c;为了方便他处理工作上的重复性且很麻烦的事情&#xff0c;程序是使用 Java 写的&#xff0c;就不得不面对一个问题&#xff1a;我必须将…

第一篇:系统分析师首篇

目录 一、目标二、计划三、完成情况1.宏观思维导图2.过程中的团队管理和其它方面的思考 四、意外之喜(最少2点)1.计划内的明确认知和思想的提升标志2.计划外的具体事情提升内容和标志 一、目标 通过参加考试&#xff0c;训练学习能力&#xff0c;而非单纯以拿证为目的。 1.在复…