leetcode:用两个栈实现一个队列

今天刷leetcode印象比较深刻的题目:用两个栈实现一个队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

 示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

 

一、题目解读

直接看例子——> 输入: ["CQueue","appendTail","deleteHead","deleteHead"] 这里是要执行的方法,从左到右执行

[[],[3],[],[]]对应上面的方法,是上面方法的参数。CQueue(创建)和deleteHead(删除)方法不需要指定数字,故参数为空,只有appendTail(添加)方法才需要指定数字

1."CQueue"创建队列,返回值为null

2."appendTail"将3压入队列,返回值为null

3."deleteHead"将队列头部的元素删除,也就是消息队列中先进来的元素,所以是deleteHead,返回该元素的数值,所以为3

4."deleteHead"继续删除队列头部的元素,但是没有元素了,所以返回-1

所以就有了下面的输出 输出:[null,null,3,-1]

 

示例 2: 输入: ["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]

[[],[],[5],[2],[],[]]

1.创建队列,返回值为null

2.删除队列头部的元素,但是没有元素,所以返回-1

3.把5压入队列,返回null

4.把2压入队列,返回null

5.删除队列头部的一个元素,也就是消息队列中先进来的元素,所以是deleteHead,就是最先进来的5,返回值为5,

6.删除队列头部的一个元素,就是后进来的2,返回值为2,

所以就有了下面的输出

输出:[null,-1,null,null,5,2]

有没有发现先进来的数字,首先显示出来了,但是题目中说要使用栈,栈是先进后出的,使用栈来实现先进先出,在这里使用两个栈就好了,从一个进来再到另一个栈,这样顺序就是

先进先出了。题目的主旨写在第一句,就是,使用两个栈实现一个队列。

 

二、解决思路

——双栈法

维护两个栈,第一个栈支持插入操作,第二个栈支持删除操作。

根据栈先进后出的特性,我们每次往第一个栈里插入元素后,第一个栈的底部元素是最后插入的元素,第一个栈的顶部元素是下一个待删除的元素。为了维护队列先进先出的特性,我

们引入第二个栈,用第二个栈维护待删除的元素,在执行删除操作的时候我们首先看下第二个栈是否为空。如果为空,我们将第一个栈里的元素一个个弹出插入到第二个栈里,这样第

二个栈里元素的顺序就是待删除的元素的顺序,要执行删除操作的时候我们直接弹出第二个栈的元素返回即可。

成员变量:维护两个栈 stack1 和 stack2,其中 stack1 支持插入操作,stack2 支持删除操作。

构造方法:初始化 stack1 和 stack2 为空。

插入元素:插入元素对应方法 appendTail,stack1 直接插入元素。

删除元素:删除元素对应方法 deleteHead

如果 stack2 为空,则将 stack1 里的所有元素弹出插入到 stack2 里
如果 stack2 仍为空,则返回 -1,否则从 stack2 弹出一个元素并返回

leetcode:用两个栈实现一个队列

 

 

三、代码实现

class CQueue {
Deque<Integer> deque1;
Deque<Integer> deque2;

public CQueue() {
deque1 = new LinkedList<>();
deque2 = new LinkedList<>();
}

public void appendTail(int value) {
deque1.push(value);
}

public int deleteHead() {
if (deque2.isEmpty()) {
while (!deque1.isEmpty()) {
deque2.push(deque1.pop());
}
}
return deque2.isEmpty() ? -1 : deque2.pop();
}
}


四、拓展——QUEUE与DEQUE的区别

平常在写leetcode经常用LinkedList向上转型Deque作为栈或者队列使用,但是一直都不知道它与Queue的区别,于是就查阅官方文档:

leetcode:用两个栈实现一个队列

 从上图可以看出,Queue以及Deque都是继承于collection,Deque是Queue的子接口。

leetcode:用两个栈实现一个队列

 从Deque的解释中,我们可以得知:Deque是double ended queue,我将其理解成双端结束的队列,双端队列,可以在首尾插入或删除元素。而Queue的解释中,Queue就是简单的FIFO队列。

所以在概念上来说,Queue是FIFO的单端队列,Deque是双端队列。

而在使用上,又有什么差别呢?从上图我们可以得知,Queue有一个直接子类PriorityQueue,而Deque中直接子类有两个:LinkedList以及ArrayDeque。

PriorityQueue:无边界的,优先级的堆(官方文档截图)

leetcode:用两个栈实现一个队列

查看源码后,明显看到PriorityQueue的底层数据结构是数组,而无边界的形容,那么指明了PriorityQueue是自带扩容机制的,具体请看PriorityQueue的grow方法。
而插入元素的时候是需要经过compareTo的处理,那么最常用就是一些范围极值的输出,类似于堆排序的用法。

LinkedList以及ArrayDeque:(官方文档截图)

leetcode:用两个栈实现一个队列

 

leetcode:用两个栈实现一个队列

 从官方解释来看,ArrayDeque是无初始容量的双端队列,LinkedList则是双向链表。ArrayDeque作为队列时的效率比LinkedList要高,而在栈的使用场景下,无疑具有尾结点不需判空的LinkedList较高效。

其实ArrayDeque和LinkedList都可以作为栈以及队列使用,但是从执行效率来说,ArrayDeque作为队列以及LinkedList作为栈使用会是更好的选择

小结:

PriorityQueue可以作为堆使用,而且可以根据传入的Comparator实现大小的调整,会是一个很好的选择。
ArrayDeque通常作为栈或队列使用,但是栈的效率不如LinkedList高。
LinkedList通常作为栈或队列使用,但是队列的效率不如ArrayQueue高。

总结

在java中,Queue被定义成单端队列使用,Deque被定义成双端队列使用。
而由于双端队列的定义,Deque可以作为栈或者队列使用,而Queue只能作为队列或者依赖于子类的实现作为堆使用。

上一篇:SAP SD基础知识之物料确定(Material Determination)


下一篇:用栈实现队列