【算法】—— 前缀和

一、区间求和问题

给定一个长度为n的序列a,有m次查询,每次查询输出一个连续区间的和。

使用暴力做法求解是将每次查询都遍历该区间求和

//暴力做法

import java.util.Scanner;


public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        int[] a = new int[100010];
        for(int i = 1;i<=n;i++){
            a[i] = scan.nextInt();
        }
        for(int i = 0;i<=m;i++){
            int l = scan.nextInt(),r = scan.nextInt(),sum=0;
            for(int j = l;j<=r;j++){
                sum+=a[j];
            }
            System.out.println(sum);
        }
    }
}

可以看到,最坏情况下,时间复杂度为 O(nm) ,这种区间求和问题就可以使用前缀和来优化

前缀和实现原理:

核心思想:

在一个新的数组上的每个元素都存储 原数组开始位置 新数组元素位置 ,最后通过 减法 来实现快速计算

原理解释:

创建一个新的数组 s ,其中每个元素 s[i] 表示原数组从开始位置到位置 i 元素之和,即

当我们需要查询 [2,3] 时,就是计算 a[2] + a[3] , 对于数组 s 来说只需要将 s[3] - s[1]

这一步的时间复杂度为 O(1)

通过上述计算可以得到公式

在创建时新数组时还可以通过迭代计算每个 s[i]

可以得到公式:

这里注意 s[1] == a[1] 但是在循环里要写成 s[1] = s[0] + a[1] ,才不会下标越界,所以数组下标都由1开始。

代码演示:

import java.util.Scanner;

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int[] a = new int[100010];
        int[] s = new int[100010];
        int n = scan.nextInt();
        int m = scan.nextInt();
        s[0] = 0;
        for(int i = 1;i<=n;i++){
            a[i] = scan.nextInt();
            s[i] = s[i-1]+a[i];
        }
        for(int i = 1;i<=m;i++){
            int l = scan.nextInt();
            int r = scan.nextInt();
            System.out.println(s[r] - s[l-1]);
        }
    }
}

这样时间复杂度就来到了 O(n+m) 。

理解了一维前缀和,我们将问题上升一个维度,来到

二维前缀和:

给定一个 n*m 大小的矩阵 A,给定 q 组查询,每次查询给定两个坐标,需要输出坐标1到坐标2的所有值

暴力做法:

import java.util.Scanner;

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int[][] a = new int[100][100];
        int n = scan.nextInt();
        int m = scan.nextInt();
        int q = scan.nextInt();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                a[i][j] = scan.nextInt();
            }
        }
        for(int i=1;i<=q;i++){
            int sum = 0;
            int x1= scan.nextInt(),y1= scan.nextInt(),x2= scan.nextInt(),y2= scan.nextInt();
            for(int j=x1;j<=x2;j++){
                for(int k=y1;k<=y2;k++){
                    sum+=a[j][k];
                }
            }
            System.out.println(sum);
        }
    }
}

中间的部分有一个三重循环,时间复杂度来到了 O(n * m * q)

依旧使用前缀和的思想来完成,在二维数组上的每个元素都要存储从开始位置的和

在二维前缀和数组中,求(x,y)就是求从(1,1)开始到(x,y)的所有和

对(x,y)做进一步拆分,会发现由四个部分组成

 先是当前点本身 a(x,y):

s[x][y-1]:

s[x-1][y]:

s[x-1][y-1]:

因为s[x-1][y-1]被加了两次,所以需要减去一次


 

综上可以得到前缀和计算公式:

s[x][y] = a[x][y] + s[x-1][y] + s[x][y-1] - s[x-1][y-1] + ;

现在起点不从(1,1)开始,计算两点表示的子矩阵的和

就可以让(x2,y2)减去两边的长方形(x2,y1-1)和 (x1-1,y2) ,因为 (x-1,y-1) 被减去两次,所以需要加上一次,得到公式:

\sum _{i=x1}^{x2}\sum _{j=y1}^{y2}A{_{i,j}}^{}

= a[x2][y2] - a[x2][y1-1] - a[x1-1][y2] + a[x1-1][y1-1] 

代码演示:

import java.util.Scanner;

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int N = 1010;
        int[][] a = new int[N][N];
        int[][] s = new int[N][N];
        int n = scan.nextInt();
        int m = scan.nextInt();
        int q = scan.nextInt();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                a[i][j] = scan.nextInt();
                //构建前缀和数组
                s[i][j] = a[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1];
            }
        }
        for(int i=1;i<=q;i++){
            int x1 = scan.nextInt(),y1 = scan.nextInt(),x2 = scan.nextInt(),y2 = scan.nextInt();
            int sum = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1];
            System.out.println(sum);
        }
    }
}

时间复杂度变为O(n*m+q)

二、区间修改问题

给定一个长度为n的序列a,有m组操作,每次操作将某一个连续区间 [ l ~ r ] 的元素都加上,最后输出操作结束后的数组a。

暴力做法为写一个循环,每次修改 [ l ~ r ] 的值,重复m次

import java.util.Scanner;

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int[] a = new int[100010];
        int n = scan.nextInt();
        int m = scan.nextInt();
        for(int i = 1;i<=n;i++){
            a[i] = scan.nextInt();
        }
        for(int i = 1;i<=m;i++){
            int l = scan.nextInt();
            int r = scan.nextInt();
            int d = scan.nextInt();
            for(int j = l;j<=r;j++){
                a[j] += d;
            }
        }
        for(int i=1;i<=n;i++){
            System.out.print(a[i] + " ");
        }
    }
}

时间复杂度为 O(nm),这种区间求和问题就可以使用差分来优化

差分实现原理:

核心思想:通过计算原数组中每个元素之间的差让元素和元素之间产生联系,再利用差不断复原出原数组。期间对差进行加减就会影响这个差之后的所有的元素

定义一个差分数组 b ,求出每一个元素之间的差

对 数组b 求前缀和 得到 新数组c

这里就可以得到一个结论,差分数组求前缀和就可以得到原数组

当我们对 b[1] + 1 后

b[1] 之后的元素都会 +1

当我们对 b[3]  - 1 后

b[3] -1 之后的元素也都 -1 和如果原来的+1相呼应就可以得到修改前的元素,最后可以得到结论:

对差分数组 b[l]+d , b[r+1]-d ,求解前缀和后,就可以得到修改后的数组c

代码演示

import java.util.Scanner;

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int[] a = new int[100010];
        int[] b = new int[100010];
        int[] c = new int[100010];
        int n = scan.nextInt();
        int m = scan.nextInt();
        for(int i = 1;i<=n;i++){
            a[i] = scan.nextInt();
            b[i] = a[i] - a[i-1];
        }
        for(int i = 1;i<=m;i++){
            int l = scan.nextInt(),r = scan.nextInt(),d = scan.nextInt();
               b[l] += d;
               b[r+1] -= d;
        }
        for(int i = 1;i<=n;i++){
            c[i] = c[i-1] + b[i];
        }
        for(int i = 1;i<=n;i++){
            System.out.print(c[i]+" ");
        }
    }
}

时间复杂度 O(n+m)

差分数组的常见性质:

  • 如果差分数组 b 除了 b[1] 以外所有值均为 0 ,则说明数组 a 的值全部都一样

差分数组还可以解决

归1问题:

一个数组 a 中共包含 n 个数,问最少多少次操作可以让 a 数组所有数都变成 1 。

操作的内容可以任选一个区间,使得区间内所有值 -1 ,数据保证一定有解

假设有一个数组【1,3,5,2,7,1】

3 变成 1 需要进行 3-1 次操作

5 变成 1 需要  4 次操作,因为 5 > 3 所以可以跟着 3 一起进行 2 次,所以只要 5-3 次

2 < 5 可以跟着 5 进行 1 次后就不用再进行,可以忽略不计

7 > 2 需要进行 7-2 次操作

综上可以得出看出,每一个数需要的操作次数可以通过减去前一个数来求出,当减去前一个数<0时,就可以忽略不计,求出每一个元素之间的差,刚好是差分数组所实现的内容,因为是归1,而第一项(a[0])是 0 ,a[1] 的操作次数会多一次,所以最后结果还需要 -1 。

代码演示:

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int[] a = new int[100010];
        int[] b = new int[100010];
        int n = scan.nextInt();
        int sum = 0;
        for(int i = 1;i<=n;i++){
            a[i] = scan.nextInt();
            b[i] = a[i] - a[i-1];
            if(b[i]>=0){
                sum += b[i];
            }
        }
        System.out.println(sum-1);
    }
}

二维差分:

给定一个 n*m 大小的矩阵 A,给定 q 组修改,每次查询给定两个坐标,需要修改坐标1到坐标2的所有值,最后打印修改完成的数组

暴力做法:

import java.util.Scanner;

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int N = 1010;
        int[][] a = new int[N][N];
        int[][] s = new int[N][N];
        int n = scan.nextInt();
        int m = scan.nextInt();
        int q = scan.nextInt();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                a[i][j] = scan.nextInt();
            }
        }
        for(int i=1;i<=q;i++){
            int x1 = scan.nextInt(),y1 = scan.nextInt(),x2 = scan.nextInt(),y2 = scan.nextInt(),d= scan.nextInt();
            for(int j=x1;j<=x2;j++){
                for(int k=y1;k<=y2;k++){
                    a[j][k] += d;
                }
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                System.out.print(a[i][j]+" ");
            }
            System.out.println();
        }
    }
}

时间复杂度为 O(n*m*q)

接下来用差分思想进行优化

首先是用差让各个元素之间产生联系,这里减去上方和左方的格子

如果只是减去左方格子就变回了一维差分

(x,y-1)和(x-1,y)也是通过减去上方和左方的格子得来

这里会发现(x-1,y-1)被多减了一次,所以计算(x,y)时还需要再加上(x-1,y-1)

通过上图描述可得公式:

差分数组 b[x][y] = a[x][y] - a[x][y-1] - a[x-1][y] + a[x-1][y-1];

有了差分数组,就可以对元素进行修改

当对差分数组修改后,所有后续有关联的元素都会发生改变

当希望只在 (x1,x2) 和 (x2,y2) 之间进行修改,就需要对其他点进行修改

对于边上的两点(x2+1,y1)、(x1,y2+1)减少 d ,对于重复减的点(x2+1,y2+1)加上 d

对点修改完后,再通过前缀和对数组复原

代码演示:

import java.util.Scanner;

public class Test {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int N = 1010;
        int[][] a = new int[N][N];
        int[][] b = new int[N][N];
        int n = scan.nextInt();
        int m = scan.nextInt();
        int q = scan.nextInt();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                a[i][j] = scan.nextInt();
                b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1] ;
            }
        }
        for(int i=1;i<=q;i++){
            int x1=scan.nextInt(),y1=scan.nextInt(),x2=scan.nextInt(),y2=scan.nextInt(),d=scan.nextInt();
            b[x1][y1]+=d;
            b[x2+1][y1]-=d;
            b[x1][y2+1]-=d;
            b[x2+1][y2+1]+=d;
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                b[i][j]=b[i][j]+b[i-1][j]+b[i][j-1]-b[i-1][j-1];
                System.out.print(b[i][j]+" ");
            }
            System.out.println();
        }
    }
}

时间复杂度为 O(n*m+q)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解下c语言下的多维数组和指针数组

在实际c语言编程中&#xff0c;三维及以上数组我们使用的很少&#xff0c;二维数组我们使用得较多。说到数组&#xff0c;又不得关联到指针&#xff0c;因为他们两者的联系太紧密了。今天我们就详细介绍下c语言下的多维数组(主要是介绍二维数组)和指针。 一、二维数组 1.1&am…

【实验】【H3CNE邓方鸣】交换机端口安全实验+2024.12.11

实验来源&#xff1a;邓方鸣交换机端口安全实验 软件下载&#xff1a; 华三虚拟实验室: 华三虚拟实验室下载 wireshark&#xff1a;wireshark SecureCRT v8.7 版本: CRT下载分享与破解 文章目录 dot1x 开启802.1X身份验证 开启802.1X身份验证&#xff0c;需要在系统视图和接口视…

leetcode-73.矩阵置零-day5

class Solution {public void setZeroes(int[][] mat) {int m mat.length, n mat[0].length;// 1. 扫描「首行」和「首列」记录「首行」和「首列」是否该被置零boolean r0 false, c0 false;for (int i 0; i < m; i) {if (mat[i][0] 0) {r0 true;break;}}for (int j …

C++ webrtc开发(非原生开发,linux上使用libdatachannel库)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、libdatachannel库的下载和build二、开始使用 1.2.引入库3.开始使用 总结 前言 使用c开发webrtc在互联网上留下的资料甚少&#xff0c;经过我一段时间的探…

windows11 专业版 docker desktop 安装指南

家庭中文版需升级专业版&#xff0c;家庭版没有hyper-v。 开始运行optionalfeatures.exe打开windows功能 安装wsl2 步骤 1 - 启用适用于 Linux 的 Windows 子系统步骤 2 - 检查运行 WSL 2 的要求步骤 3 - 启用虚拟机功能步骤 4 - 下载 Linux 内核更新包 步骤 1 - 启用适用于 L…

解锁前端开发速度的秘密武器【Vite】

在前端开发的江湖中&#xff0c;有人偏爱 Webpack 的强大与稳定&#xff0c;有人钟情于 Rollup 的轻量与高效。而 Vite&#xff0c;这个后来居上的工具&#xff0c;却以“极致的快”和“极简的易”赢得了开发者的芳心。众所周知万事都有缘由&#xff0c;接下来我们就来深度剖析…

AI发展与LabVIEW程序员就业

人工智能&#xff08;AI&#xff09;技术的快速发展确实对许多行业带来了变革&#xff0c;包括自动化、数据分析、软件开发等领域。对于LabVIEW程序员来说&#xff0c;AI的崛起确实引发了一个值得关注的问题&#xff1a;AI会不会取代他们的工作&#xff0c;导致大量失业&#x…

决策曲线分析(DCA)中平均净收益用于评价模型算法(R自定义函数)

决策曲线分析&#xff08;DCA&#xff09;中平均净收益用于评价模型算法 DCA分析虽然不强调用来评价模型算法或者变量组合的优劣&#xff0c;但是实际应用过程中感觉DCA曲线的走势和模型的效能具有良好的一致性&#xff0c;其实这种一致性也可以找到内在的联系&#xff0c;比如…

【Linux】:多线程(POSIX 信号量 、基于环形队列的生产消费者模型)

&#x1f4c3;个人主页&#xff1a;island1314 ​​ &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 目录 1. POSIX 信号量…

人工智能的历史概况和脉络

人工智能( AI ) 的历史始于古代&#xff0c;当时有神话、故事和谣言称&#xff0c;人工生物被工匠大师赋予了智慧或意识。从古代到现在&#xff0c;对逻辑和形式推理的研究直接导致了20 世纪 40 年代可编程数字计算机的发明&#xff0c;这是一种基于抽象数学推理的机器。这种设…

昇思25天学习打卡营第33天|共赴算力时代

文章目录 一、平台简介二、深度学习模型2.1 处理数据集2.2 模型训练2.3 加载模型 三、共赴算力时代 一、平台简介 昇思大模型平台&#xff0c;就像是AI学习者和开发者的超级基地&#xff0c;这里不仅提供丰富的项目、模型和大模型体验&#xff0c;还有一大堆经典数据集任你挑。…

基于32单片机的RS485综合土壤传感器检测土壤PH、氮磷钾的使用(超详细)

1-3为RS485综合土壤传感器的基本内容 4-5为基于STM32F103C8T6单片机使用RS485传感器检测土壤PH、氮磷钾并显示在OLED显示屏的相关配置内容 注意&#xff1a;本篇文件讲解使用的是PH、氮磷钾四合一RS485综合土壤传感器&#xff0c;但里面的讲解内容适配市面上的所有多合一的RS…

Vue入门到精通:运行环境

Vue入门到精通&#xff1a;运行环境 Vue3的运行环境搭建主要有两种方法&#xff1a;一种是直接在页面中引入Vue库&#xff0c;另一种是通过脚手架工具创建Vue项目。 &#xff08;一&#xff09;页面直接引入Vue库 页面直接引入Vue库的方法&#xff0c;是指在HTML网页中通过s…

PHP项目从 php5.3 版本升级到 php8.3 版本时的一些问题和解决方法记录

一个原来的项目&#xff0c;因为业务需要&#xff0c;进行了PHP版本升级&#xff0c;从php5.3直接升级到php8.3。变化挺大的&#xff0c;原程序中有很多不再兼容&#xff0c;在此处进行一下记录。 一、Deprecated: 显式转换问题 报错内容&#xff1a;Deprecated: Implicit con…

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

文章目录 PyQt5 超详细入门级教程前言序篇&#xff1a;1-3部分&#xff1a;PyQt5基础与常用控件第1部分&#xff1a;初识 PyQt5 和安装1.1 什么是 PyQt5&#xff1f;1.2 在 PyCharm 中安装 PyQt51.3 在 PyCharm 中编写第一个 PyQt5 应用程序1.4 代码详细解释1.5 在 PyCharm 中运…

Apache Kylin最简单的解析、了解

官网&#xff1a;Overview | Apache Kylin 一、Apache Kylin是什么&#xff1f; 由中国团队研发具有浓厚的中国韵味&#xff0c;使用神兽麒麟&#xff08;kylin&#xff09;为名 的一个OLAP多维数据分析引擎:&#xff08;据官方给出的数据&#xff09; 亚秒级响应&#xff…

【工具】linux matlab 的使用

问题1 - 复制图表 在使用linux matlab画图后&#xff0c;无法保存figure。 例如在windows下 但是在linux下并没有这个“Copy Figure”的选项。 这是因为 “ The Copy Figure option is not available on Linux systems. Use the programmatic alternative.” 解决方案&…

opencv——(图像梯度处理、图像边缘化检测、图像轮廓查找和绘制、透视变换、举例轮廓的外接边界框)

一、图像梯度处理 1 图像边缘提取 cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) 功能&#xff1a;用于对图像进行卷积操作。卷积是图像处理中的一个基本操作&#xff0c;它通过一个称为卷积核&#xff08;或滤波器&#xff09;的小矩阵在图像上…

Redis - 集合 Set 及代码实战

Set 类型 定义&#xff1a;类似 Java 中的 HashSet 类&#xff0c;key 是 set 的名字&#xff0c;value 是集合中的值特点 无序元素唯一查找速度快支持交集、并集、补集功能 常见命令 命令功能SADD key member …添加元素SREM key member …删除元素SCARD key获取元素个数SI…

androidstudio导入项目至成功运行(保姆级教程)

目录 一、下载资源包 二、创建一个新的项目 三、替换配置文件 四、打开Android Studio 一、下载资源包 将你得到的资源包下载到你能够找到的位置然后解压&#xff0c;方便操作 二、创建一个新的项目 如果已经有新创建的项目就不用新建了&#xff0c;但是需要注意的是&am…