单调数据结构小结

版权声明:All rights reserved. https://blog.csdn.net/qq_42814118/article/details/81362522

由于最近做了几道单调数据结构,有点感慨,来做个小结。
单调数据结构这里主要指单调队列、单调栈两个。

基本特征

在队列/栈的基础上增加了“单调性”这一要求。即保持数据结构中的元素按某种值的大小顺序单调。

由于栈/队列的插入规则不能变,又要让元素能成功插入,所以要保持单调性,它们只能丢弃原有的某些信息 来维护单调性。

单调栈

保持单调性的方法:插入元素时,如果当前顶端和即将插入的元素之间单调性冲突,则将顶端pop,直到栈空或者顶端符合单调性。

例1-模拟

给定 n 个整数
依次放入“保持从顶到底递减”的单调栈中,要求输出单调栈最后的形态,按照“自顶向下”顺序输出。

例如:将 1 4 2 3 5 7 压入上述的单调栈,
1、4进入,都没有破坏单调性,2要进入,但是2作为栈顶会大于4,所以4要弹出,2进入
这样插入完,自顶向下的最终形态就是 7 5 3 2 1。

理解完单调栈的工作模式,接下来来看看它的应用

例2 - 单调栈的应用

给定 n 个整数 a i ,对于每个数,求一个最小的整数 k ,使得 a i + k a i 。如果不存在输出-1。 n 100000

例2解答

暴力的话对于每个数往右找,复杂度平方。
我们想想重复枚举了什么?对于 a i 1 枚举过的数,我们在处理 a i 时很可能又把它枚举了一遍,这就浪费了。
我们要利用问题的单调性质。你找到一个数 a i + k a i 的话,比 a i 小的那些元素的答案也都可能是这个。
因此我们维护一个单调栈,如对于序列 5 4 2 1 3 2 4

  • 首先5入栈,4比5小,4入栈,同理2入栈,1入栈
  • 遇到3时,3比栈顶1大,因此 a n s 4 = 1 ,求完后将1弹出,发现3比栈顶2大,因此 a n s 3 = 2 ,弹出2,发现4比3大,3入栈(由于栈是单调的,4大于3,那么在4下面的栈的元素一定都大于3)
  • 2入栈,比3小
  • 4入栈,比2大, a n s 6 =1,2出,比3大, a n s 5 = 2 ,3出,大等于4, a n s 2 = 5 ,比5小,4入栈
  • 找不到剩下的数了,栈内的元素都找不到到答案,赋值为-1

由于每个元素都进出栈一次,是线性的复杂度。
体会到优化在哪了吗?感觉就像“带着一堆元素一起往右移动”,遇到一个门槛就会被筛掉一些一样hhh。

单调队列

那单调队列比单调栈多了什么呢?可以这样理解,单调队列是单调栈的“升级”。
我们知道,队列不但允许在尾端插入,也允许在头端删除。而单调队列是一种特殊队列,它支持“尾端插入、删除”,“头端删除”。只考虑尾端的话,它和单调栈一毛一样。但是它多了“头端删除”。
如果当前队列的“头”超出了问题规模,可以将头扔掉。这就是单调队列更高明之处。

例3 - 滑动窗口问题-单调队列应用

有一个长度为 n 的数列,求所有长度为 m 的区间中的最大值和最小值,按区间左端点的大小顺序输出。
m n 100000

例3解答

暴力复杂度 O ( n m ) ,和刚刚那个例题一样也有很多重复状态,我们想想是否能优化。
对于每个区间,如果我们能极快地将这个区间保存为有序数列,那么直接输出两端点即可,但是这样每次加入排序都是 O ( m   l o g   m ) 的,有没有什么办法能不要“每次都加入排序”呢?
因为只关心最值,我们便想到单调数据结构。比如我们用两个单调栈,一个递增,一个递减。
我们把数列的前 m 个数塞进这俩单调栈里,然后输出这两个栈的栈底,OK第一个区间搞定,接下来是第二个区间,这个区间只比第一个区间多了右边一个数,少了左边一个数。多了数好说,直接塞进单调栈里更新就完了,少了怎么办?单调栈能删除指定的元素嘛?
那如果少了的元素不删会怎么样?
假设第一个区间最小值刚刚好是1号元素,到第二个区间了,新加的元素仍不比1号元素大,那么1号元素还是在栈顶,你会输出一个不存在在第二个区间的最小值。
那我看到栈底已经不属于范围了,难道我就眼睁睁的看着它被输出,不能把它删掉然后输出下一个栈底吗?
可以是可以,但它已经不叫单调栈了,单调栈只能从栈顶操作,对栈底是不能动的。
但是单调队列可以。单调队列中,“栈底”“栈顶”改名叫“队首”“队尾”了。
对于这题,我们每次维护一个单调队列,新加一个元素,就按照单调栈的规则插到队尾,然后如果队首不在范围内,删除队首,直到遇到范围内的队首。
由于每个元素进出队一次,复杂度也是线性的。

总结

为什么单调栈和单调队列能优化效率呢?
不要只看复杂度分析里的“进出栈/队一次”,
其本质原因就是,它们都 舍弃了一些信息
而这些信息,是完全没贡献,或者能被更优的元素完全继承它的信息。
比如例2,我们发现较小的元素是可以跟着较大的元素走的,因为“比较大元素还要大的元素是较小元素答案的边界”。
要维护单调性,就要扔掉一些元素,这些元素恰好是已经处理完了的。
所以遇到“区间”、“最值”这样的东西,单调数据结构有可能会帮上你一把。

猜你喜欢

转载自blog.csdn.net/qq_42814118/article/details/81362522
今日推荐