重温数据结构与算法之前缀和

文章目录

  • 前言
  • 一、基础
    • 1.1 定义
    • 1.2 时间复杂度
  • 二、扩展
    • 2.1 二维前缀和
    • 2.2 差分数组
    • 2.3 前缀积
  • 三、LeetCode 实战
    • 3.1 长度最小的子数组
    • 3.2 二维区域和检索 - 矩阵不可变
  • 参考

前言

前缀和(Prefix Sum),也被称为累计和,是一种在计算机编程算法领域中广泛应用的重要概念和技巧。它通过将一个序列中的元素累加起来,得到一个新的序列,其中每个元素表示原序列中对应位置及其之前所有元素的和。前缀和的简洁性和高效性使其在各种算法和问题中有着广泛的应用。

前缀和有许多实际的应用。例如,前缀和可以用于计算区间内的和。无论是静态区间查询还是动态更新的场景,前缀和都可以为我们提供快速的求解方法。它可以在常数时间内计算出任意区间的和,而不受区间长度的影响。这种特性使得前缀和在处理数据流问题时非常有用。

在本文中,我们将深入探讨前缀和的基础知识、应用案例以及优化和扩展技巧。通过学习和掌握这些内容,读者将能够充分理解前缀和的概念、原理和实际应用,为解决各种算法和数据处理问题提供有力的工具和思路。

一、基础

1.1 定义

对于给定的数组 a ,它的前缀和数组 prefix 的第 i 个元素 prefix[i] 就是原数组 a 从第一个元素累加到第 i 个元素的总和,公式如下:
p r e f i x [ i ] = ∑ k = 0 i − 1 a [ k ] , 其中 1 ≤ i ≤ n , n 为数组 a 长度 prefix[i] = \sum_{k=0}^{i-1} a[k], 其中 1 \leq i \leq n,n为数组a长度 prefix[i]=k=0i1a[k],其中1in,n为数组a长度

在计算前缀和数组时,一般会在原数组的开头加上一个初始值 0,以便在计算任意区间的元素和时能够统一处理。因此,前缀和数组的长度通常会比原数组的长度多 1。当计算前缀和数组时, 可以直接通过递推的方式求出
p r e f i x [ i ] = p r e f i x [ i − 1 ] + a [ i ] prefix[i] = prefix[i-1] + a[i] prefix[i]=prefix[i1]+a[i]
通过预先计算出原数组的前缀和数组,我们可以在快速得到任意区间 ([l,r]) 的元素和
sum ( l , r ) = p r e f i x [ r ] − p r e f i x [ l − 1 ] \text{sum}(l, r) = prefix[r] - prefix[l-1] sum(l,r)=prefix[r]prefix[l1]

1.2 时间复杂度

在计算前缀和时,我们可以通过简单的迭代和累加操作来获得相应的结果。这使得前缀和的计算过程非常高效,时间复杂度为 O ( n ) O(n) O(n)

快速得到任意区间 [ l , r ] [l,r] [l,r] 的元素和只需要将 prefix 数组内两个元素相减,时间复杂度为 O ( 1 ) O(1) O(1)

二、扩展

2.1 二维前缀和

二维前缀和是在二维数组中应用前缀和技巧的一种方法,用于高效地计算二维数组中指定子矩阵的元素和。类似于一维前缀和,二维前缀和也可以帮助我们以较低的时间复杂度快速计算出任意子矩阵的元素和。

假设我们有一个二维数组 a,其大小为 m × n m \times n m×n,我们可以通过以下方式计算二维前缀和数组 prefix:
p r e f i x [ i ] [ j ] = ∑ r = 0 i − 1 ∑ c = 0 j − 1 a [ r ] [ c ] , 其中 1 ≤ i ≤ m , 1 ≤ j ≤ n prefix[i][j] = \sum_{r=0}^{i-1} \sum_{c=0}^{j-1} a[r][c], 其中1 \leq i \leq m,1 \leq j \leq n prefix[i][j]=r=0i1c=0j1a[r][c],其中1im,1jn
计算二维前缀和也可通过递推获得
p r e f i x [ i ] [ j ] = p r e f i x [ i − 1 ] [ j ] + p r e f i x [ i ] [ j − 1 ] − p r e f i x [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] - prefix[i - 1][j-1] + a[i][j] prefix[i][j]=prefix[i1][j]+prefix[i][j1]prefix[i1][j1]+a[i][j]

例子如下:

prefix_sum_00

同样,通过预先计算出二维数组的前缀和数组,我们可以在 O ( 1 ) O(1) O(1) 的时间复杂度内得到任意子矩阵 [ r 1 : r 2 , c 1 : c 2 ] [r1:r2, c1:c2] [r1:r2,c1:c2] 的元素和
sum ( r 1 , c 1 , r 2 , c 2 ) = p r e f i x [ r 2 ] [ c 2 ] − p r e f i x [ r 2 ] [ c 1 − 1 ] − p r e f i x [ r 1 − 1 ] [ c 2 ] + p r e f i x [ r 1 − 1 ] [ c 1 − 1 ] \text{sum}(r_1, c_1, r_2, c_2) = prefix[r_2][c_2] - prefix[r_2][c_1-1] - prefix[r_1-1][c_2] + prefix[r_1-1][c_1-1] sum(r1,c1,r2,c2)=prefix[r2][c2]prefix[r2][c11]prefix[r11][c2]+prefix[r11][c11]

举个例子:

prefix_sum_01

2.2 差分数组

差分数组是一种在处理频繁更新的数组时常用的技巧,它可以帮助我们以较低的时间复杂度完成对原数组的部分元素进行增减操作,并且能够快速还原出原数组。

差分数组的定义很简单,对于原数组 a,其差分数组 d 的每个元素 d[i] 表示原数组 a[i] 与 a[i-1] 之间的差值:$ d[i] = a[i] - a[i-1] $

通过差分数组,我们可以通过对差分数组的部分元素进行修改来实现对原数组的部分元素进行增减操作。例如,如果我们想要将原数组 a)的某个区间 [l, r] 的所有元素增加一个固定值 val,我们可以将差分数组的 d[l] 增加 val,而将差分数组的 d[r+1] 减去 val。这样,在还原原数组时,我们可以通过累加差分数组的前缀和得到:$ a[i] = a[i-1] + d[i] $

需要注意的是,为了计算差分数组,我们需要对原数组的首位元素 a[0])进行特殊处理。通常情况下,我们会在差分数组的第一个位置 d[1])上存储 a[0] 的值,即 d[1] = a[0]。这样,在还原原数组时,我们可以通过累加差分数组的前缀和并加上 a[0] 得到:
a [ i ] = a [ 0 ] + ∑ k = 1 i d [ k ] a[i] = a[0] + \sum_{k=1}^{i} d[k] a[i]=a[0]+k=1id[k]
差分数组的优点在于,它可以在 O ( 1 ) ) O(1)) O(1))的时间复杂度内完成对原数组的部分元素增减操作,并且能够快速还原出原数组。因此,当我们需要频繁对原数组进行增减操作时,使用差分数组可以提高算法的效率。

2.3 前缀积

类似前缀和,还有前缀积,后缀和,后缀积等等,相关推导公式不再列出。

前缀积和后缀积用于高效地计算出数组中每个位置左/右侧所有元素的乘积,后缀和计算数组中每个位置右侧所有元素的和。

三、LeetCode 实战

3.1 长度最小的子数组

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

这题求连续子数组长度,可以先计算前缀和,由于都是正整数,前缀和数组是递增的,这时就可以使用二分法求以遍历的i为起点,子数组终点总和大于等于target的最小长度,每一位都要求,加上二分法,时间复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

public int minSubArrayLen(int target, int[] nums) {
    int [] prefix = new int[nums.length + 1];
    for (int i = 0; i < nums.length; i++) {
        prefix[i + 1] = prefix[i] + nums[i];
    }
    if (prefix[nums.length] < target) return  0;
    int start = 1;
    int end = nums.length;
    int ans = Integer.MAX_VALUE;
    for (int i = 1; i <= nums.length; i++) {
        start = i;
        end = nums.length;
        while (start <= end) {
            int mid = start + (end - start) / 2;
            if (prefix[mid] - prefix[i - 1] >= target) {
                ans = Math.min(ans, mid - i + 1);
                end = mid - 1;
            }  else {
                start = mid + 1;
            }
        }
    }
    return ans;
}

3.2 二维区域和检索 - 矩阵不可变

304. 二维区域和检索 - 矩阵不可变

给定一个二维矩阵 matrix,以下类型的多个请求:

  • 计算其子矩形范围内元素的总和,该子矩阵的 左上角(row1, col1)右下角(row2, col2)

实现 NumMatrix 类:

  • NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
  • int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1)右下角 (row2, col2) 所描述的子矩阵的元素 总和

经典的二维前缀和题目

class NumMatrix {

    int [][] prefix;

    public NumMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        prefix = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1] + matrix[i - 1][j - 1] - prefix[i - 1][j - 1]; 
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return prefix[row2 + 1][col2 + 1] - prefix[row1][col2 + 1] - prefix[row2 + 1][col1] + prefix[row1][col1];
    }
}

参考

  1. 前缀和
  2. https://leetcode.cn/tag/prefix-sum/problemset/

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

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

相关文章

SQL必知会(二)-SQL查询篇(5)-用通配符进行过滤

第6课、用通配符进行过滤 LIKE&#xff1a;匹配文本 LIKE&#xff1a;针对未知值进行过滤。通配符搜索只能用于文本字段。 1&#xff09;百分号%通配符 %表示任何字符出现任意次数。 需求&#xff1a;找出所有以词 Fish 起头的产品 SELECT prod_id, prod_name FROM Product…

浅谈高并发以及三大利器:缓存、限流和降级

引言 高并发背景 互联网行业迅速发展&#xff0c;用户量剧增&#xff0c;系统面临巨大的并发请求压力。 软件系统有三个追求&#xff1a;高性能、高并发、高可用&#xff0c;俗称三高。三者既有区别也有联系&#xff0c;门门道道很多&#xff0c;全面讨论需要三天三夜&#…

Aria2 任意文件写入漏洞复现

漏洞描述 Aria2 是一款轻量级、多协议、多源下载工具&#xff08;支持 HTTP/HTTPS、FTP、BitTorrent、Metalink&#xff09;&#xff0c;内置 XML-RPC 和 JSON-RPC 接口。 我们可以使用 RPC 接口来操作 aria2 并将文件下载到任意目录&#xff0c;从而造成任意文件写入漏洞。 …

Nginx常用配置与命令,nginx代理转发配置

Nginx特点 高并发、高性能; 模块化架构使得它的扩展性非常好; 异步非阻塞的事件驱动模型这点和 Node.js 相似; 相对于其它服务器来说它可以连续几个月甚至更长而不需要重启服务器使得它具有高可靠性; 热部署、平滑升级; 完全开源,生态繁荣; Nginx作用 Nginx 的最重要的…

【Excel】补全单元格值变成固定长度

我们知道股票代码都为6位数字&#xff0c;但深圳中小板代码前面以0开头&#xff0c;数字格式时前面的0会自动省略&#xff0c;现在需要在Excel表格补全它。如下图&#xff1a; 这时我们需要用到特殊的函数&#xff1a;TEXT或者RIGHT TEXT函数是Excel中一个非常有用的函数。TEX…

SpringBoot项目调用openCV报错:nested exception is java.lang.UnsatisfiedLinkError

今天在通过web项目调用openCV的时候提示如下错误&#xff1a; nested exception is java.lang.UnsatisfiedLinkError:org.opencv.imgcodecs.Imgcodecs.imread_0(Ljava/la如下图所示&#xff1a; 但是通过直接启动java main函数确正常&#xff0c;初步诊断和SpringBoot热加载…

dart packages 版本问题解决 和 对 pubspenc.lock 的深入了解

先讲讲我遇到的问题 在进行写项目的时候&#xff0c;我需要用到一个依赖 这个依赖是 3.0.0 版本的&#xff0c;但是实际上我本地的上存在多个版本 虽然我修改了 pubspenc.yaml 文件中需要的依赖&#xff0c;但是每次使用的还是 3.4.0 版本的依赖&#xff0c;但是我需要的是 3…

Windows查看端口占用情况

Windows如何查看端口占用情况 方法1. cmd命令行执行netstat命令&#xff0c;查看端口占用情况 netstat -ano 以上命令输出太多信息&#xff0c;不方便查看&#xff0c;通过如下命令搜索具体端口占用情况&#xff0c;例如&#xff1a;8080端口 netstat -ano | findstr "…

手摸手入门Springboot+Grafana10.2接收JSON

JSON&#xff08;JavaScript Object Notation, JS对象简谱&#xff09;是一种轻量级的数据交换格式。它基于 ECMAScript&#xff08;European Computer Manufacturers Association, 欧洲计算机协会制定的js规范&#xff09;的一个子集&#xff0c;采用完全独立于编程语言的文本…

面向对象基础(以python语言为例)

1、定义一个类&#xff1b;实例化类的对象&#xff1b;调用类中的方法 #定义一个类 class Student:#类方法&#xff08;即函数&#xff09;def study(self,course_name):print(f学生正在学习{course_name})def play(self):print("xx学生正在玩游戏")#实例化&#xf…

Rust的崛起:现代必备编程语言,是时候该考虑加入学习了

在不断变化的编程环境中&#xff0c;新的语言和框架如雨后春笋般涌现&#xff0c;需要一个真正强大且设计良好的工具才能脱颖而出。在这些工具中&#xff0c;Rust 已成为效率、安全性和性能的灯塔。从它作为 Mozilla 的一个副项目到它在软件行业中不可否认的增长&#xff0c;Ru…

【沐风老师】3DMAX克隆修改器插件教程

3DMAX克隆修改器插件&#xff0c;它通过增量平移、旋转和缩放输入几何体来创建对象的副本。在某些方面&#xff0c;它类似于 3ds Max 的内置阵列工具&#xff0c;但有一个主要优点 -克隆是完全参数化的&#xff0c;因此您可以随时更改重复项的数量及其分布。其他功能包括随机变…

基于单片机的空调智能控制器的设计

**单片机设计介绍&#xff0c;基于单片机的空调智能控制器的设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的空调智能控制器需要具备输入输出端口、定时器、计数器等模块&#xff0c;以便对空调进行精确控制。下…

【Linux(0)】为什么要学习Linux,为什么互联网公司在招聘时,会提出要有Linux经验,及其使用;一些Linux常见指令

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…

Cordova插件开发三:通过广播实现应用间跨进程通信

文章目录 1.最终效果预览2.数据发送3.插件接受数据4.JS页面中点击获取数据返回1.最终效果预览 场景说明:我们给自来水公司开发了一个h5应用,需要对接第三方厂家支持硬件设备以便于获取到高精度定位数据,之前几篇文件写过,我已经集成过南方测绘RTK和高精度定位模块的设备,厂…

Android11修改连接WiFi后AP端显示的设备名

修改build.prop文件 1.修改 /system/build.prop 最后添加&#xff0c;xxx 为自己设置的设备名&#xff1a; net.hostnamexxx 2. 重启、重连wifi&#xff0c;从热点或路由器后台查看设备名即为修改后的名称 代码里动态配置 暴力手段&#xff1a;grep -rn “net.hostname” *…

LeetCode18-四数之和

注意!其中nums数值的范围,四个加一起会导致INT溢出,long类型则是64位的整数,因此不会导致溢出,这也是本题难点之一! 大佬解法(拿捏offer的解法) 经过反复的代码比对和Debug,发现大佬解法的速度之快体现在足足7个if语句的剪枝,其中包括了2个关键性的去重的if语句以及2个关键性…

我的前端笔记JS

js介绍 js是编程语音&#xff0c;之前学的html和css是标记语言 百度搜索mdn官网就可以 语法 输出、对话框、控制台日志、输入对话框 字面量 简单理解就是看到的内容是属于什么类型&#xff0c;例如1232&#xff0c;这个是属于数字字面量

僵尸进程问题如何处理

现象&#xff1a; 工作中遇到docker内有很多的僵尸进程&#xff0c;导致CPU过高&#xff0c;直接卡死。 原因&#xff1a; 每个进程都有一个唯一的标识&#xff0c;称为 pid&#xff0c;pid 是一个非负的整数值&#xff0c;使用 ps 命令可以查看其中 PID 是表示进程号。系统中…

C语言数据结构-----链表类型详解及链表练习题

0.前言 之前我讲解了循序表以及单链表&#xff0c;接下来我会在介绍几个不同的链表&#xff0c;并举例相关习题使大家能够更加深入的理解。 前期内容如下&#xff1a; 链接: 顺序表(动态顺序表增删查改的代码实现) 链接: 单链表(无头单向不循环)增删查改的代码实现 链接: [双向…