一命通关差分


 本章节是前缀和的延申

 

一命通关前缀和-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_74260823/article/details/136530291?spm=1001.2014.3001.5501

 一命通关前缀和


公交车

引入

还是利用我们在前缀和中所采用的例子——公交车。

有一辆公交车,一共上下了N批乘客:

  1. 第一批2个人,第一站上车,第五站下车
  2. 第二批6个人,第三站上车,第六站下车
  3. 第三批4个人,第二站上车,第五站下车
  4. ... 

问,在不查公交车监控的情况下,公交车到达每一站的时候车上还剩下几个人?

这个问题,传统的解决方法很直接,我们创建一个数组

vector<int> platform(k),k为数组大小车站的数目

用来表示每一站的时候,车上还剩几个人。

而对每一批上下车的乘客,假设他们有people个人在第up站上车,在第off站下车,他们在车上的站数为[up,off),也就是说,数组i到j中的所有元素都要加上他们的人数people

 用以上方法,来表示前三批人:

假设有N批人,公交车一共有k站,每一批人都是第一站上最后一站下,那最坏的时间复杂度是多少?O(Nk)。有没有办法把这个解法优化一下,让每一批人的计算都变成O(1)呢?当然有:

优化

我们想让每一批人的计算都变成O(1),那就想办法,最大化利用第up站上和第off站下的信息。我们有没有办法,只需要表示第i站和第j站的人数变化,就能表示所有站的人数变化?

如果你有办法,就不会继续看这篇文章了。直接说结论吧:当然有。我们来重新挖掘一下这个信息:

  • 第up站上people人,表示公交车上从第up站开始,往后都多了people个人
  • 第off站下people人,表示公交车上从第off站开始,往后都少了people个人
  • 而在off站之后,多people个人和少people个人加在一起,最终是0个人,也就是第up站上第off站下,只对[up,off)这些站台会产生影响,对其他站台产生不了任何影响,从而得到了我们的传统解法。

我们利用这个信息,再创立一个数组:

vector<int> dif(k)

用来表示每一站和前一站的人数差dif[i]=platform[i]-platform[i-1]

而再来通过这个数组去整理题目的信息:

  • 第二站上了四个人,也就是从第二站开始,比第二站之前都要多出四个人,自然第二站比第一站要多四个人,dif[2]+=4
  • 第三站和第二站,都比第一站多四个人,第二站和第三站的差没有变化,故dif[3]不变
  • 第五站下了四个人,也就是从第五站开始,比第五站之前都要少四个人,自然第五站比第四战要少四个人,dif[5]-=4 
  • 同理,第六站和第五站的差也没有发生变化,故dif[6]不变 

通过这个,我们自然可以总结出规律:

这样,每一批的上下车,时间复杂度都优化为了O(1)

但是,有人可能就要问了,海老师海老师,这个数组有什么用啊?这又不是最终的答案
别急,往后有反转

求解 

假设,公交车有一个第0站,第0站的人数肯定是0人

  • 我们知道了第1站和第0站的人数差,第一站的人数很容易求出来platform[1]=platform[0]+dif[1] 
  • 而接着往后,第二站和第一站的人数差我们也知道,自然platform[2]=platform[1]+dif[2] 
  • 很容易便找到规律:对于每一站i,都可以求得
    platform[i]=platform[i-1]+dif[i] 

而把所有的platform展开,就能得到:
platform[i]=platform[0]+dif[1]+dif[2]+...+dif[i]\displaystyle 

也就是,platform的值为dif数组i之前所有元素的和

好了,跳出公交车。我们通过这个解答,发现platform就是前缀和数组,而对前缀和数组的每一项求差得到的dif数组,我们称之为差分数组。我们发现了platform[i]=platform[i-1]+dif[i]
platform[i]-platform[i-1]=nums[i]

也就是,dif[i]和nums[i]其实是一回事,这就是我们常说的

好了,知道这句话其实卵用没有,我们直接来看代码吧。

代码及公式

//假设一个数组 vector<int> people中有三个元素[up,off,people]
//现在给了一个二元数组 vector<vector<int>> nums,大小为n,表示有N批乘客
//求解每一站的乘客人数

//自然,初始化两个数组,platform和dif
vector<int> platform(n+1);//因为假定了一个第0站,所以多开一块空间
vector<int> dif(n+1);//差分数组

for(auto& e:nums)
{
    int up=e[0];
    int off=e[1];
    int people=e[2];
    
    dif[up]+=people;
    dif[off]-=people;
    //每一次都是O(1),一共有n次,时间复杂度为O(n)
}

//再来求前缀和
for(int i=1;i<k+1;i++)
    platform[i]=platform[i-1]+dif[i];
//时间复杂度为站台数量O(k)
//总的时间复杂度为O(n+k)

这类题目的特征在于,每一次操作都对一块区间内的所有元素进行同样的操作。
面对这样的问题,我们先用差分数组,来把这些同样的操作,只表示出其区间首尾的变化
然后对差分数组求出前缀和,就能表示出总的变化

这便是差分数组的公式。

好了,有了这个公式,去乱杀差分的题目吧:

1109. 航班预订统计 - 力扣(LeetCode)

2251. 花期内花的数目 - 力扣(LeetCode)


  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值