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

    文章

    4455

    浏览量

    90751
  • 差分
    +关注

    关注

    0

    文章

    53

    浏览量

    21228
  • 数组
    +关注

    关注

    1

    文章

    409

    浏览量

    25595

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

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

收藏 人收藏

    评论

    相关推荐

    while的循环里面,怎么样可以通过控件去让其中的数组值全部初始化?

    这种方式是让所有的数据都初始化了,我只需要部分数据初始化,就是所有的数组,大佬们求帮助!
    发表于 04-18 21:19

    深入探索KUKA KRL中的数组应用

    如果 CHAR 类型数组的所有数组元素都拥有相同的字符串,则不必单独初始化每个数组元素。忽略右侧的数组下标。(对于一维数组下标,不写下标。)
    的头像 发表于 04-18 10:37 136次阅读
    深入探索KUKA KRL中的<b class='flag-5'>数组</b>应用

    PHP中数组的使用方法!

    PHP中数组的使用方法! PHP是一种广泛使用的网络编程语言,它的数组功能非常强大且灵活。数组是一种数据结构,它允许我们在单个变量中存储多个值。 在本篇文章中,我将详细解释PHP数组
    的头像 发表于 01-12 15:11 161次阅读

    数组和指针不相同吗?数组和指针有哪些区别

    数组就是指针,指针就是数组,这样的言论在评论区看到不下于10次。
    的头像 发表于 12-13 16:34 352次阅读
    <b class='flag-5'>数组</b>和指针不相同吗?<b class='flag-5'>数组</b>和指针有哪些区别

    数组与指针不能混用的情况

    数组与指针不能混用的情况  数组与指针是 C/C++ 中非常常见的特性和概念。然而,在某些情况下,数组与指针是不能混用的。这种情况通常涉及到数组的内存分配和
    的头像 发表于 12-07 13:46 310次阅读

    C语言中数组的用法

    C语言的数组是一种数据结构,它可以存储多个相同类型的数据,例如整数,字符,浮点数等。数组的每个元素都有一个索引,用来表示它在数组中的位置。数组的索引从0开始,也就是说,
    的头像 发表于 11-24 17:48 837次阅读
    C语言中<b class='flag-5'>数组</b>的用法

    c语言中数组怎么定义

    C语言中,数组是一种用来存储相同类型元素的数据结构。它可以存储多个元素,并通过一个共同的名称来引用这些元素。数组是一种很重要的数据结构,可以用于解决很多实际的问题。 在C语言中,定义数组的语法如下
    的头像 发表于 11-24 10:11 1077次阅读

    将一维数组转为二维python

    将一维数组转为二维数组是一个常见的问题,特别是在处理数据时。一维数组是由一个连续的数据块组成,而二维数组则是由多个一维数组组成的
    的头像 发表于 11-23 14:54 2203次阅读

    什么是数组数组有什么用?数组的使用方法

    数组(Array)是有序的元素序列。
    的头像 发表于 11-08 14:58 703次阅读
    什么是<b class='flag-5'>数组</b>?<b class='flag-5'>数组</b>有什么用?<b class='flag-5'>数组</b>的使用方法

    数组的定义 什么是数组

    数组 数组是内置类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。 在初始化后长度是固定的,无法修改其长度。当作为方法的参数传入时将复制一份数组而不是引用同一指针。
    的头像 发表于 10-09 09:39 1336次阅读

    为什么把u16参数赋值给u16的数组,然后把数组打印出来数值对不上?

    请问为什么把u16参数赋值给u16的数组,然后把数组打印出来数值对不上? 我把u8的参数
    发表于 09-22 07:34

    C++数组名和数组拷贝详解

    C++数组间赋值不能直接通过数组名称 randy = sesame进行,因为数组名并不是指针,大部分情况下,编译器会隐式转换为指向数组首元素的指针常量。
    发表于 08-21 15:09 298次阅读
    C++<b class='flag-5'>数组</b>名和<b class='flag-5'>数组</b>拷贝详解

    C语言中指针数组数组指针的区别

    指针和数组之间存在着紧密的关系。在本文中,我们将探讨指针和数组的关系、指针算术和数组遍历、多维数组与指针以及指针数组
    发表于 08-17 15:29 280次阅读

    C 语言数组的基本结构

    数组是最基本的数据结构,关于数组的面试题也屡见不鲜,本文罗列了一些常见的面试题,仅供参考。目前有以下18道题目。 数组求和 求数组的最大值和最小值 求
    的头像 发表于 06-22 10:56 381次阅读

    美的柜机双模

    美的
    YS YYDS
    发布于 :2023年05月26日 17:23:43