💬 前言
本文主要是记录我在Leetcode上对题号225——队列实现栈的解题过程。
🏷 简介
本题要求使用两个队列实现一个后入先出(LIFO)的栈(MyStack类),并支持普通队列的全部四种操作(push
、top
、pop
和 empty
)。
所以全文中心点是使用队列实现栈操作。
1. 思路一: 双队列实现栈
1.1 核心思想
使用两个队列,一个为主队列,名为队列一;另一个为主队列的缓存队列,名为队列二。
当进行栈的出栈时,将队列一里除末尾元素外其他元素出队并缓存至队列二,然后将末尾元素单独拉出来出队并return
,完成后将缓存在队列二里的非末尾元素恢复至队列一,从而实现了和栈操作相同的效果。
1.2 具体实现
既然是用队列实现,首先就是要开一个队列的成员变量空间:
private Queue<Integer> queue;
/**
* Initialize your data structure here.
*/
public MyStack() {
queue = new LinkedList<Integer>();
}
在这里我们使用Java
自带的LinkedList
来实现队列,LinkedList
也就是链表。
然后将栈的empty
方法实现,非常简单,使用队列自带的操作即可:
/**
* Returns whether the stack is empty.
*/
public boolean empty() {
return queue.isEmpty();
}
MyStack
的入栈操作,也就等于将元素推入队列:
public void push(int x) {
queue.add(x);
}
出栈操作在队列的实现上有点复杂,但并不难。需要先通过循环操作将末尾元素外的其他元素都出队并缓存到队列二里面,然后再把最后的元素单独拿出来出队,所以实际上真正出队的只有末尾元素;最后再将缓存在队列二里的非末尾元素重新放入主队列:
public int pop() {
// 创建缓存队列 queue2
Queue<Integer> queue2 = new LinkedList<>();
// 除了最后一个元素,将 queue 中的所有元素放入 queue2
while (queue.size() > 1) {
queue2.add(queue.remove());
}
// 此时 queue 中剩下的最后一个元素就是「栈顶」元素
int ret = queue.remove();
// 此时 queue2 缓存的数据赋值给 queue
queue = queue2;
return ret;
}
一旦我们实现了 pop()
方法,那么实现 top()
就简单了。我们可以复用我们已经实现的 pop()
,将栈顶元素拿出来,记录下来,作为返回值。然后因为 top()
不删除元素,我们再将这个值放进队列就好了。
public int top() {
int ret = pop();
push(ret);
return ret;
}
1.3 复杂度分析
方法 | 具体值 | 备注 |
---|---|---|
push() | O(1) | 向队列enqueue() |
pop() | O(n) | 因循环整个队列,直至取到最后一个元素,所以复杂度是O(n)级 |
top() | O(n) | 因为复用了pop() 方法,复杂度也是O(n)级 |
empty() | O(1) | 复用队列自带的isEmpty() 方法 |
2.思路二:单队列实现栈
2.1 核心思想
在向队列推入元素时,同时将队列除末尾元素外的其他元素都出队并再入队,将整个队列倒置过来,这样子最新入队的元素永远是队首位,在出队时也将必是队首,从而达到和栈操作一样的后进先出的效果。
2.2 具体实现
本思路相较思路一代码简洁,复杂度也降低了,是一种良好的优化写法。步骤简单,所以只用代码说明。
public class MyStack {
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<Integer>();
}
public void push(int x) {
// 入队
queue.offer(x);
for (int i = 0;i < queue.size() - 1;i++) {
// 再将非末尾的元素一一出队并再入队到末尾
// 如此一来,最先入队的元素总在队首位,所以也会最先出队,从而达到后进先出
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
3.2 复杂度分析
方法 | 具体值 | 备注 |
---|---|---|
push() | O(n) | 承担了倒置队列的职责,所以复杂度也上升了 |
pop() | O(1) | 复用队列的poll() 方法 |
top() | O(1) | 复用队列的peek() 方法 |
empty() | O(1) | 复用队列的isEmpty() 方法 |
📝 3. 总结
- 思路二在
push()
阶段倒置队列,将最新加入的元素放到了队首,从而能起到后进先出( LIFO )的效果。相应的,其他栈操作方法也只需要调用队列的对应方法,时间复杂度降低到了O( 1 )。 - 思路一把主要操作在
pop()
方法里,特别是top()
方法还复用了最复杂的pop()
方法,整体复杂度有一定上升。 - 思路二较思路一在想法上简单,在实现上也不用多写复杂的代码,整体复杂度上也低了不少。所以更推荐使用方法二。