Processing math: 100%

队列相关问题

两个栈实现队列

该问题是“有2个空栈s_1,s_2,出入栈运算都是O(1)代价的,要求用这2个栈实现一个队列,出入队运算都是O(1)摊还代价的”。用2个栈实现队列的思路很简单,把一组元素入栈s_1再出栈入栈到s_2就能把s_1的栈底变成s_2的栈顶,进而就能实现队头的出队。这里具体来看如何实现,首先对于入队运算,直接入栈s_1即可,对于出队运算,当s_2为空时把s_1的元素全部出栈并依次入栈s_2,最后弹出s_2的栈顶作为队头出队。当s_2非空,直接弹出s_2的栈顶作为队头出队即可。

上述过程中,每个元素的入队都是1次s_1的入栈,出队都是3次的s_1出栈、s_2入栈、s_2出栈操作。对于2n次队列操作,最好的情况是发生2n次入队操作,最坏的情况是发生n次入队、n次出队,对于最坏的情况,也仅意味着发生了4n次栈操作。所以此时的总代价是O(n),每次操作的摊还代价是O(1)。下面是具体程序实现。

 

队列的最大值(单调队列算法)

e48469aa-01d9-4de5-9ada-88810b63f598

该问题是“实现1个队列,其支持求当前最大值的max运算,且出入队和max的摊还代价是O(1)”。该问题看起来与栈的最值问题类似,但栈每次pop后能回到上次push前,队列没有该性质,不易找到O(1)max实现,但容易找到O(1)摊还代价的实现,比如用2个可O(1)代价求最大值的栈模拟队列。这里讨论另一种实现,各运算的实现步骤如下:

0) 初始化:创建空的普通队列q、空的双端队列dq,其中dq能查看左右队头;

1) put(x): x入队q。从右队头循环的出队dqdq非空且右队头小于x。最后从dq右队头入队x

2) get(): 从q出队,出队元素计为x。此时dq也非空,若其最左队头等于x,则左队头出队1次。最后返回x

3) max():返回dq最左端元素;

想要分析上述步骤,必须先明确dq的意义,这里通过“命题+证明”讨论。

a) 设队列的dq = (x_0,x_1…x_n)有至少1个元素,任意x_i都在其q中且顺序相同。当dq只有1个元素时必须满足qx_0前的元素都小于x_0。当dq多于1个元素时必须满足x_{i-1} \geq x_i,且qx_{i-1},x_i间的元素都小于x_ix_0前的元素都小于x_0

b.1) 对于初始队列(q,dq为空)或满足(a)的队列,执行1次put(x)后满足(a);

证明:初始队列显然可以。对于非初始队列,若x > x_0x成为新dq队头,新dq只留有x这个元素,满足(a)。若x_n \geq xx成为新dq队尾且不导致任何dq元素出队,满足(a)。若x_{i-1} \geq x > x_ix会使dqx_i和其后面的全部元素出队,并取代x_i位置。从q的角度看,x是其新队尾,而x_{i-1}x间的元素全部小于x,因为x大于原来dq中的x_i,x_{i+1}…x_n,也满足(a)。

b.2) 对于满足(a)的队列,执行1次get后满足(a)或成为初始队列(q,dq为空);

证明:设get的元素为x,若x不等于x_0x只能是qx_0前的元素,dq前后无变化,满足(a)。若x等于x_0,出队的x就是x_0本身,q,dq会同时出队x_0,新dq = (x_1,…x_n),新qx_1前的元素都是原来x_0,x_1间的元素,所以满足(a)。

根据(b)的2条结论可以知道,初始化队列后,其经过任意的get/put后不是初始队列(q,dq为空)就是满足(a)的。对于满足(a)的队列,其dq的左队头是dq中最大的,而dq中所有元素都是其在q“划分的区间”里的最大值,故dq的左队头是q中的最大值,进而max能正确返回最大值。更直观的可以看上面的图片,该图片是同一个队列中q,dq的对比。

在上述队列中,双端队列dq中的元素时刻被维护为从左到右单调递减的,这种队列称为单调队列,使用了单调队列的算法称为单调队列算法,是一类比较灵活的队列算法。下面讨论各操作的时间复杂度,设q,dq所有运算都是O(1)的,那么max,get也是O(1)的,对于初始队列,如果执行kputget/put,则至多有O(k)个元素出入队,而每个元素从入队到出队,至多从q,dq各出入队1次,故总代价是O(k),摊还代价是O(1)。下面是程序实现。

 

滑动窗口的最大值

415fa760-7460-4439-9c38-5bd318f83ff9

该问题是“给定1个数值数组nums,1个固定的滑动窗口大小值k(窗口容纳的元素数),其中k不大于nums的长度,这个滑动窗口会像上图一样从左到右,一次一个元素的滑动。要求从左到右的求出每个窗口的最大值,以数组形式返回”。很多滑动窗口型的问题都可以用队列算法解决,这个问题就可以用前面讨论过的最大值队列/单调队列直接解决。

这里分析下时间复杂度,这时的最大值队列是供另一个算法使用的。设nums大小为n,最大值队列共执行nputn-kget,这时最大值队列的O(1)摊还代价就能看作O(1)实际代价,总代价是O(n)。具体程序如下。

 

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注