标题:用栈实现队列
出处:232. 用栈实现队列
4 级
请你仅使用两个栈实现一个先进先出的队列。实现的队列应该支持普通队列的全部操作( push \texttt{push} push、 pop \texttt{pop} pop、 peek \texttt{peek} peek 和 empty \texttt{empty} empty)。
实现 MyQueue \texttt{MyQueue} MyQueue 类:
注意:
示例 1:
输入:
["MyQueue",
"push",
"push",
"peek",
"pop",
"empty"]
\texttt{["MyQueue", "push", "push", "peek", "pop", "empty"]}
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[],
[1],
[2],
[],
[],
[]]
\texttt{[[], [1], [2], [], [], []]}
[[], [1], [2], [], [], []]
输出:
[null,
null,
null,
1,
1,
false]
\texttt{[null, null, null, 1, 1, false]}
[null, null, null, 1, 1, false]
解释:
MyQueue
myQueue
=
new
MyQueue();
\texttt{MyQueue myQueue = new MyQueue();}
MyQueue myQueue = new MyQueue();
myQueue.push(1);
\texttt{myQueue.push(1);}
myQueue.push(1);
myQueue.push(2);
\texttt{myQueue.push(2);}
myQueue.push(2);
myQueue.peek();
\texttt{myQueue.peek();}
myQueue.peek(); // 返回
1
\texttt{1}
1
myQueue.pop();
\texttt{myQueue.pop();}
myQueue.pop(); // 返回
1
\texttt{1}
1
myQueue.empty();
\texttt{myQueue.empty();}
myQueue.empty(); // 返回
False
\texttt{False}
False
你能否实现每个操作均摊时间复杂度为 O(1) \texttt{O(1)} O(1) 的队列?换句话说,执行 n \texttt{n} n 个操作的总时间复杂度为 O(n) \texttt{O(n)} O(n),即使其中一个操作可能花费较长时间。
由于队列的特点是先进先出,因此使用栈实现队列时,需要维护栈内的元素顺序,在出队操作和查看队首元素操作时可以定位到最先添加的元素。
如果不考虑时间复杂度的限制,一种可行的解法是,每次入队操作时维护栈内的元素顺序,使得从栈顶到栈底的元素符合添加顺序。具体做法是,使用两个栈,其中 stack 1 \textit{stack}_1 stack1 的作用是存储元素, stack 2 \textit{stack}_2 stack2 的作用是辅助操作,入队操作如下:
将 stack 1 \textit{stack}_1 stack1 的全部元素依次出栈并入栈到 stack 2 \textit{stack}_2 stack2;
将待添加的元素入栈到 stack 1 \textit{stack}_1 stack1;
将 stack 2 \textit{stack}_2 stack2 的全部元素依次出栈并入栈到 stack 1 \textit{stack}_1 stack1。
完成上述操作后, stack 2 \textit{stack}_2 stack2 为空, stack 1 \textit{stack}_1 stack1 的栈底元素是新添加的元素, stack 1 \textit{stack}_1 stack1 内的其余元素和顺序不变。因此上述操作的效果是在 stack 1 \textit{stack}_1 stack1 的栈底增加了新添加的元素, stack 1 \textit{stack}_1 stack1 的栈顶和栈底分别对应队首和队尾。
由于每次入队操作都维护了 stack 1 \textit{stack}_1 stack1 内的元素顺序,因此出队操作和查看队首元素操作的时间复杂度是 O ( 1 ) O(1) O(1),只要对 stack 1 \textit{stack}_1 stack1 的栈顶元素执行相应的操作即可(移除并返回元素,或者返回元素但不移除)。
判断队列是否为空操作只要判断 stack 1 \textit{stack}_1 stack1 是否为空即可。
上述做法虽然可以做到出队操作、查看队首元素操作和判断队列是否为空操作的时间复杂度是 O ( 1 ) O(1) O(1),但是当队列内有 n n n 个元素时,入队操作需要 2 n 2n 2n 次出栈操作和 2 n + 1 2n + 1 2n+1 次入栈操作,因此入队操作的时间复杂度是 O ( n ) O(n) O(n)。
为了做到均摊时间复杂度是 O ( 1 ) O(1) O(1),需要使用优化的做法,将 stack 1 \textit{stack}_1 stack1 用于入队操作,将 stack 2 \textit{stack}_2 stack2 用于出队操作和查看队首元素操作。
对于入队操作,将待添加的元素入栈到 stack 1 \textit{stack}_1 stack1。
对于出队操作和查看队首元素操作,做法如下:
判断 stack 2 \textit{stack}_2 stack2 是否为空,如果为空,则将 stack 1 \textit{stack}_1 stack1 的全部元素依次出栈并入栈到 stack 2 \textit{stack}_2 stack2;
对 stack 2 \textit{stack}_2 stack2 的栈顶元素执行相应的操作(移除并返回元素,或者返回元素但不移除)。
判断队列是否为空操作需要判断 stack 1 \textit{stack}_1 stack1 和 stack 2 \textit{stack}_2 stack2 是否都为空。
优化的做法中,入队操作和判断队列是否为空操作的时间复杂度是 O ( 1 ) O(1) O(1),对于出队操作和查看队首元素操作,由于每个元素最多在两个栈都入栈和出栈各一次,因此均摊时间复杂度是 O ( 1 ) O(1) O(1)。
对于优化的做法的正确性证明,需要证明出队操作和查看队首元素操作返回的元素是最先入队的元素。
由于入队操作时将待添加的元素入栈到 stack 1 \textit{stack}_1 stack1,因此 stack 1 \textit{stack}_1 stack1 从栈底到栈顶的元素顺序符合入队顺序。
出队操作和查看队首元素操作时,如果 stack 2 \textit{stack}_2 stack2 为空,则将 stack 1 \textit{stack}_1 stack1 的全部元素依次出栈并入栈到 stack 2 \textit{stack}_2 stack2,此时 stack 2 \textit{stack}_2 stack2 内的元素顺序和 stack 1 \textit{stack}_1 stack1 内的元素顺序相反, stack 2 \textit{stack}_2 stack2 的全部元素中,栈顶元素是最先入队的元素。
由于所有的元素在入栈到 stack 2 \textit{stack}_2 stack2 之前首先会入栈到 stack 1 \textit{stack}_1 stack1,因此 stack 2 \textit{stack}_2 stack2 内的元素的入队时间一定比 stack 1 \textit{stack}_1 stack1 的入队时间早, stack 2 \textit{stack}_2 stack2 的栈顶元素是队列的全部元素中最先入队的元素。
class MyQueue {
Deque<Integer> stack1;
Deque<Integer> stack2;
public MyQueue() {
stack1 = new ArrayDeque<Integer>();
stack2 = new ArrayDeque<Integer>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
preparePop();
return stack2.pop();
}
public int peek() {
preparePop();
return stack2.peek();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
private void preparePop() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
}
}
时间复杂度:构造方法的时间复杂度是 O ( 1 ) O(1) O(1),入队和判断队列是否为空操作的时间复杂度都是 O ( 1 ) O(1) O(1),出队和查看队首元素操作的均摊时间复杂度都是 O ( 1 ) O(1) O(1)。每个元素最多在两个栈都入栈和出栈各一次。
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是队列内元素个数。需要使用栈存储元素。