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

news2024/10/7 6:47:36

文章目录

  • 前言
  • 一、基础
    • 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/

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

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

相关文章

【EI会议征稿】第三届新能源技术创新与低碳发展国际研讨会(NET-LC 2024)

第三届新能源技术创新与低碳发展国际研讨会&#xff08;NET-LC 2024&#xff09; 2024 3rd International Symposium on New Energy Technology Innovation and Low Carbon Development 先进的现代能源技术对世界各地的经济发展至关重要。持续的经济进步取决于安全、可靠和负担…

idea2023如何查看被使用上下文关系

1.接口查看实现类&#xff0c;实现类查看接口 查看接口所有实现类 根据类里面的方法查看被覆盖的接口中的方法 2.查看方法、类被调用 3.查看类的继承关系

ChatGPT的图识别来了

前几天ChatGPT推出了Dall-E 3功能&#xff0c;可以根据文字和描述一段话来生成一个或者一组图。 这次又来重磅了&#xff0c;图识别又来了&#xff01;换句话说&#xff0c;也即是文生图&#xff0c;图生文都可以实现了&#xff0c;一起来试试 1、解释图中的意思 &#xff0…

粤嵌实训医疗项目--day06(Vue + SpringBoot)

往期回顾 粤嵌实训医疗项目(小组开发)--day05-CSDN博客粤嵌实训医疗项目--day04&#xff08;Vue SpringBoot&#xff09;-CSDN博客粤嵌实训医疗项目--day03&#xff08;Vue SpringBoot&#xff09;-CSDN博客粤嵌实训医疗项目day02&#xff08;Vue SpringBoot&#xff09;-CS…

Python 测试框架 Pytest 的入门

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;加入1000人软件测试技术学习交流群&#x1f4e2;资源分享&#xff1a;进了字节跳动之后&#xff0c;才…

【算法练习Day44】最长递增子序列最长连续递增序列最长重复子数组

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 最长递增子序列最长连续递增…

线程池创建、执行、销毁的原理解析

目录 线程池的执行原理线程执行参考&#xff1a; 线程池的执行原理 假设最大核心数是2&#xff0c;非核心线程数为1&#xff0c;队列长度是3 来第一个任务的时候&#xff0c;没有工作线程在工作&#xff0c;需要创建一个 来第二个任务的时候&#xff0c;发现当前核心线程数…

C语言 UTF-8与GBK编码互转,一个头文件搞定!不使用第三方库,纯C语言跨平台

背景&#xff1a; 项目需要在mips32架构的freertos系统上将输入的汉字、标点符号、数字、字母等转成机内码输出&#xff0c;第一个想到的办法是移植第三方库进行编码转换&#xff0c;网上搜了一圈libiconv、ICU库都可以实现&#xff0c;但是在我的项目上移植编译各种报错、非常…

【漏洞复现】jumpserver任意密码重置

jumpserver任意密码重置漏洞-CVE-2023-42820 jumpserver jumpserver是FIT2CLOUD飞致云旗下的开源堡垒机。 环境搭建 这里用的是vulhub靶场 进入 jumpserver 的目录 修改配置文件 config.env 里面的 DOMAINS 参数为kali的地址 运行环境&#xff0c;第一次运行的话会拉取文件…

rocksdb 中 db_bench 的使用方法

硬件要求 硬件要求如表1所示。 表1 硬件要求 项目 说明 CPU 12 * AMD Ryzen 5 5500U with Radeon Graphics 内存 DDR4 磁盘 HDD 软件要求 软件要求如表2所示。 表2 软件要求 项目 版本 说明 下载地址 CentOS 7.6 操作系统。 Download kernel 4.14.0 内核。…

射频功率放大器有哪些用途

射频功率放大器是一种专用于放大射频信号的设备&#xff0c;它在现代通信、广播、雷达、无线电频谱监测和科学研究等领域中发挥着重要的作用。射频功率放大器能够将输入的低功率射频信号放大为较高功率的信号&#xff0c;以满足各种应用场景对信号传输距离、质量和稳定性的要求…

基于VPLC711的曲面外观检测XYR运动控制解决方案

市场应用背景 随着消费升级&#xff0c;产品形态正在朝着多样性和精细化方向迅速发展。这导致了对于复杂曲面轨迹加工的需求&#xff0c;包括外观检测、打磨抛光和点胶工艺控制&#xff0c;要求更高的精密度。企业必须主动满足市场需求&#xff0c;不断改进工艺&#xff0c;以…

Fabric: 使用InvokeChaincode实现跨通道数据访问

因为工作中遇到一些问题考虑使用Fabric的跨通道链码调用方法InvokeChaincode()来解决&#xff0c;这篇文章主要是记录以下在Fabric测试网络中InvokeChaincode()的使用过程及遇到的问题。 1 前期准备 1.1 认识InvokeChaincode InvokeChaincode的作用是调用指定的链码。而被调用…

数字滤波器分析---相位响应

数字滤波器分析---相位响应 MATLAB 函数可用于提取滤波器的相位响应。在给定频率响应的情况下&#xff0c;函数 abs 返回幅值&#xff0c;angle 返回以弧度为单位的相位角。要使用 fvtool 查看巴特沃斯滤波器的幅值和相位&#xff0c;请使用&#xff1a; d designfilt(lowpa…

Linux应用开发基础知识——Framebuffer 应用编程(四)

前言&#xff1a; 在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。Frame 是帧的意 思&#xff0c;buffer 是缓冲的意思&#xff0c;这意味着 Framebuffer 就是一块内存&#xff0c;里面保存着 一帧图像。Framebuffer 中保存着一帧图像的每一个像素颜色值&#xff0c;假设…

2023年9月少儿编程 中国电子学会图形化编程等级考试Scratch编程二级真题解析(选择题)

2023年9月scratch编程等级考试二级真题 选择题(共25题,每题2分,共50分) 1、点击绿旗,运行程序后,舞台上的图形是 A、画笔粗细为4的三角形 B、画笔粗细为5的六边形 C、画笔粗细为4的六角形 D、画笔粗细为5的三角形 答案:D 考点分析:考查积木综合使用,重点考查画笔…

100 寻找重复数

寻找重复数 题解1 二分法题解2 快慢指针(同环形链表2(ab)(ab)kL) 给定一个包含 n 1 个整数的数组 nums &#xff0c;其数字都在 [1, n] 范围内&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 &#xff0c;返…

LDR6023AQ-PDHUB最简外围成本低搂到底就是干

USB-C PD协议里&#xff0c;SRC和SNK双方之间通过CC通信来协商请求确定充电功率及数据传输速率。当个设备需要充电时&#xff0c;它会发送消息去给适配器请求充电&#xff0c;此时充电器会回应设备的请求&#xff0c;并告知其可提供的档位功率&#xff0c;设备端会根据适配器端…

@JSONField或@JsonProperty注解使用

一、需求 使用JSONField或JsonProperty注解&#xff0c;来解决bean与json字段不一致问题&#xff0c;或者字段定义不符合前端所需要的标准&#xff0c;最近在项目中发现实体类属性中&#xff0c;同时使用了JSONField和JsonProperty注解&#xff0c;用于重新声明属性key。有时候…

数字滤波器分析---频率响应

数字滤波器分析---频率响应 幅值、相位、冲激和阶跃响应、相位和群延迟、零极点分析。 分析滤波器的频域和时域响应。可视化复平面中的滤波器极点和零点。 频率响应 数字域 freqz 使用基于 FFT 的算法来计算数字滤波器的 Z 变换频率响应。具体来说&#xff0c;语句 [h,w]…