0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一个小而美的算法技巧:差分数组

算法与数据结构 来源:labuladong 作者:labuladong 2020-09-21 15:54 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

本文给大家介绍一个小而美的算法技巧:差分数组。

读完本文,你可以去解决力扣第 1109 题「航班预订统计」,难度Medium

差分数组技巧是前文前缀和技巧详解写过的前缀和技巧的兄弟。

前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。

没看过前文没关系,这里简单介绍一下前缀和,核心代码就是下面这段:

classPrefixSum{ //前缀和数组 privateint[]prefix; /*输入一个数组,构造前缀和*/ publicPrefixSum(int[]nums){ prefix=newint[nums.length+1]; //计算nums的累加和 for(inti=1;i< prefix.length; i++) {             prefix[i] = prefix[i - 1] + nums[i - 1];         }     }     /* 查询闭区间 [i, j] 的累加和 */     public int query(int i, int j) {         return prefix[j + 1] - prefix[i];     } }

prefix[i]就代表着nums[0..i-1]所有元素的累加和,如果我们想求区间nums[i..j]的累加和,只要计算prefix[j+1] - prefix[i]即可,而不需要遍历整个区间求和。

本文讲一个和前缀和思想非常类似的算法技巧「差分数组」,差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。

比如说,我给你输入一个数组nums,然后又要求给区间nums[2..6]全部加 1,再给nums[3..9]全部减 3,再给nums[0..4]全部加 2,再给…

一通操作猛如虎,然后问你,最后nums数组的值是什么?

常规的思路很容易,你让我给区间nums[i..j]加上val,那我就一个 for 循环给它们都加上呗,还能咋样?这种思路的时间复杂度是 O(N),由于这个场景下对nums的修改非常频繁,所以效率会很低下。

这里就需要差分数组的技巧,类似前缀和技巧构造的prefix数组,我们先对nums数组构造一个diff差分数组,diff[i]就是nums[i]和nums[i-1]之差:

int[]diff=newint[nums.length]; //构造差分数组 diff[0]=nums[0]; for(inti=1;i< nums.length; i++) {     diff[i] = nums[i] - nums[i - 1]; }

通过这个diff差分数组是可以反推出原始数组nums的,代码逻辑如下:

int[]res=newint[diff.length]; //根据差分数组构造结果数组 res[0]=diff[0]; for(inti=1;i< diff.length; i++) {     res[i] = res[i - 1] + diff[i]; }

这样构造差分数组diff,就可以快速进行区间增减的操作,如果你想对区间nums[i..j]的元素全部加 3,那么只需要让diff[i] += 3,然后再让diff[j+1] -= 3即可:

原理很简单,回想diff数组反推nums数组的过程,diff[i] += 3意味着给nums[i..]所有的元素都加了 3,然后diff[j+1] -= 3又意味着对于nums[j+1..]所有元素再减 3,那综合起来,是不是就是对nums[i..j]中的所有元素都加 3 了?

只要花费 O(1) 的时间修改diff数组,就相当于给nums的整个区间做了修改。多次修改diff,然后通过diff数组反推,即可得到nums修改后的结果。

现在我们把差分数组抽象成一个类,包含increment方法和result方法:

classDifference{ //差分数组 privateint[]diff; publicDifference(int[]nums){ assertnums.length>0; diff=newint[nums.length]; //构造差分数组 diff[0]=nums[0]; for(inti=1;i< nums.length; i++) {             diff[i] = nums[i] - nums[i - 1];         }     }     /* 给闭区间 [i,j] 增加 val(可以是负数)*/     public void increment(int i, int j, int val) {         diff[i] += val;         if (j + 1 < diff.length) {             diff[j + 1] -= val;         }     }     public int[] result() {         int[] res = new int[diff.length];         // 根据差分数组构造结果数组         res[0] = diff[0];         for (int i = 1; i < diff.length; i++) {             res[i] = res[i - 1] + diff[i];         }         return res;     } }

这里注意一下increment方法中的 if 语句:

publicvoidincrement(inti,intj,intval){ diff[i]+=val; if(j+1< diff.length) {         diff[j + 1] -= val;     } }

当j+1 >= diff.length时,说明是对nums[i]及以后的整个数组都进行修改,那么就不需要再给diff数组减val了。

算法实践

这里看一下力扣第 1109 题「航班预订统计」:

函数签名如下:

int[]corpFlightBookings(int[][]bookings,intn)

这个题目就在那绕弯弯,其实它就是个差分数组的题,我给你翻译一下:

给你输入一个长度为n的数组nums,其中所有元素都是 0。再给你输入一个bookings,里面是若干三元组(i,j,k),每个三元组的含义就是要求你给nums数组的闭区间[i-1,j-1]中所有元素都加上k。请你返回最后的nums数组是多少?

PS:因为题目说的n是从 1 开始计数的,而数组索引从 0 开始,所以对于输入的三元组(i,j,k),数组区间应该对应[i-1,j-1]。

这么一看,不就是一道标准的差分数组题嘛?我们可以直接复用刚才写的类:

int[]corpFlightBookings(int[][]bookings,intn){ //nums初始化为全0 int[]nums=newint[n]; //构造差分解法 Differencedf=newDifference(nums); for(int[]booking:bookings){ //注意转成数组索引要减一哦 inti=booking[0]-1; intj=booking[1]-1; intval=booking[2]; //对区间nums[i..j]增加val df.increment(i,j,val); } //返回最终的结果数组 returndf.result(); }

这道题就解决了。

其实我觉得差分数组和前缀和数组都是比较常见且巧妙的算法技巧,分别适用不同的常见,而且是会者不难,难者不会。所以,关于差分数组的使用,你学会了吗?!

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 算法
    +关注

    关注

    23

    文章

    4761

    浏览量

    97165
  • 差分
    +关注

    关注

    0

    文章

    65

    浏览量

    21787
  • 数组
    +关注

    关注

    1

    文章

    420

    浏览量

    27114

原文标题:论那些小而美的算法技巧:差分数组/前缀和

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    数组的初体验

    程序中也需要容器,只不过该容器有点特殊,它在程序中是块连续的,大小固定并且里面的数据类型致的内存空间,它还有好听的名字叫数组。可以将数组
    发表于 11-25 08:06

    SM4算法实现分享(算法原理

    解密算法与加密算法采用同结构,只是轮密钥使用的顺序不同,加密采用顺序的,解密采用逆序的。 SM4加密算法是典型的分组密码
    发表于 10-30 08:10

    RISC-V 算法原理及串口通信

    具体方法 算法原理: 由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧或三帧图像进行分运算,不同帧对应的像素点相减,判断灰度的绝对值,当绝
    发表于 10-28 06:16

    50-600 MHz,12 dB 100 Ω 分数字衰减器 skyworksinc

    电子发烧友网为你提供()50-600 MHz,12 dB 100 Ω 分数字衰减器相关产品参数、数据手册,更有50-600 MHz,12 dB 100 Ω 分数字衰减器的引脚图、接
    发表于 05-23 18:33
    50-600 MHz,12 dB 100 Ω <b class='flag-5'>差</b><b class='flag-5'>分数</b>字衰减器 skyworksinc

    50-600 MHz、6 dB 100 Ω 分数字衰减器 skyworksinc

    电子发烧友网为你提供()50-600 MHz、6 dB 100 Ω 分数字衰减器相关产品参数、数据手册,更有50-600 MHz、6 dB 100 Ω 分数字衰减器的引脚图、接线图
    发表于 05-23 18:32
    50-600 MHz、6 dB 100 Ω <b class='flag-5'>差</b><b class='flag-5'>分数</b>字衰减器 skyworksinc

    种基于分数阶 PID 直流电机调速的 AGV 控制系统

    电传感器实现避障,并采用上位机对其进行监控。为达到 AGV 电机调速的稳定性与实时性,采用分数阶 PID 算法进行控制,通过 Matlab软件进行建模与仿真,验证其可行性。最后,经实际应用场合验证,AGV 小车具有抗干扰能力强,避障精度高,运行稳定安全等优点。 纯分享贴,
    发表于 03-25 15:10

    PID控制算法的C语言实现:PID算法原理

    在工业应用中 PID 及其衍生算法是应用最广泛的算法,是当之无愧的万能算法,如果能够熟练掌握 PID 算法的设计与实现过程,对于
    发表于 02-26 15:24

    ADS1256四路分输出读取数值间的干扰问题求解答

    =AIN1.并通过发送SYNC 命令紧接WAKEUP 命令从新启动转换进程.命令之间的时间满足手册要求,接着利用RDATA命令读取数据,该数据应该是 AIN6与AIN7间的电压值,将其存入数组Data【3】;. 不断循环b)
    发表于 01-10 06:30

    把ADC当成伪分输入,ADC负端给固定偏置,这是否合适?

    在我小项目中有数据采集单元,要采集的是单端信号。
    发表于 01-06 06:15

    TimSort:在标准函数库中广泛使用的排序算法

    排序算法呢? 本文将带你走进 TimSort,在标准函数库中广泛使用的排序算法。 这个算法由工程师 Tim Peters 于 2001
    的头像 发表于 01-03 11:42 962次阅读

    有内部模式让ADS8363里面有两AD,能AD工作在全分,工作在伪分吗?

    PDE位只能控制工作2 x 2 fully-differential和4 x 2 pseudo-differential。 有内部模式让ADS8363里面有两AD,能AD工作在全
    发表于 01-02 07:57

    分信号传输中的噪声抑制

    在高速通信和电子系统中,信号的完整性对于系统性能至关重要。分信号传输作为种有效的信号传输方式,因其出色的噪声抑制能力受到青睐。 分信号传输的基本原理
    的头像 发表于 12-25 17:21 1589次阅读

    美的携手亚马逊云科技,提升全球客户体验

    近日,亚马逊云科技宣布了项重要合作成果:全球领先的科技企业美的已成功应用Amazon Connect,在海外14国家和地区迅速且成功地部署了云端全渠道客户联络中心。 这部署不仅展
    的头像 发表于 12-24 11:48 893次阅读

    数组的下标为什么可以是负数

    最近有同学发来这样段代码,并提出问题,数组的下标为什么可以是负数?     #include int main(){ const char *s = "helloworld";
    的头像 发表于 12-20 11:18 869次阅读

    AFE4404 INP和INM这两引脚为什么需要分输入,这种差分输入的好处是?

    问题1、INP和INM这两引脚为什么需要分输入,这种差分输入的好处是? 问题2、 这个分处理 咱们是INP和INM这两管脚的分间
    发表于 12-12 06:50