# 【Day 5】设计一个支持增量操作的栈
# 题目描述
使用栈实现队列的下列操作:
push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
说明:
你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-queue-using-stacks
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
# 我的回答
https://github.com/leetcode-pp/91alg-1/issues/21#issuecomment-639208274
# 解法一
# 时空复杂度
push O(1) 、pop O(n) 、peekO(1)、 empty(1)
# 空间复杂度 O(n)
维护两个栈,分别用来进行 pop
和 push
操作,push 操作很常规,重点是如何 pop,
当遇到pop
操作时,将pushstack
的顶部 push
到popstacck
的底部,这样popstack顶部
就是最早进栈的数据,有popstack
就进行正常的pop
操作就可以了
/**
* Initialize your data structure here.
*/
var MyQueue = function () {
this.pushstack = []
this.popstack = []
};
/**
* Push element x to the back of queue.
* @param {number} x
* @return {void}
*/
MyQueue.prototype.push = function (x) {
this.pushstack.push(x)
};
/**
* Removes the element from in front of queue and returns that element.
* @return {number}
*/
MyQueue.prototype.pop = function () {
if (!this.popstack.length) {
while (this.pushstack.length) {
this.popstack.push(this.pushstack.pop())
}
}
return this.popstack.pop()
};
/**
* Get the front element.
* @return {number}
*/
MyQueue.prototype.peek = function () {
if (this.popstack.length) return this.popstack[this.popstack.length - 1]
return this.pushstack[0]
};
/**
* Returns whether the queue is empty.
* @return {boolean}
*/
MyQueue.prototype.empty = function () {
return !this.pushstack.length && !this.popstack.length
};
# 参考回答
# 题目地址
# 232. 用栈实现队列 (opens new window)
# 题目描述
使用栈实现队列的下列操作:
push(x) -- 将一个元素放入队列的尾部。 pop() -- 从队列首部移除元素。 peek() -- 返回队列首部的元素。 empty() -- 返回队列是否为空。 示例:
MyQueue queue = new MyQueue();
queue.push(1); queue.push(2); queue.peek(); // 返回 1 queue.pop(); // 返回 1 queue.empty(); // 返回 false 说明:
你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 假设所有操作都是有效的、 (例如,一个空的队列不会调用 pop 或者 peek 操作)。
# 前置知识
- 栈
- 队列
# 思路
题目要求用栈的原生操作来实现队列,也就是说需要用到 pop 和 push 但是我们知道 pop 和 push 都是在栈顶的操作,而队列的 enque 和 deque 则是在队列的两端的操作,这么一看一个 stack 好像不太能完成。
我们来分析一下过程。
假如向栈中分别 push 四个数字
1, 2, 3, 4
,那么此时栈的情况应该是:
如果此时按照题目要求 pop 或者 peek 的话, 应该是返回 1 才对,而 1 在栈底我们无法直接操作。如果想要返回 1,我们首先要将 2,3,4 分别出栈才行。
然而,如果我们这么做,1 虽然是正常返回了,但是 2,3,4 不就永远消失了么? 一种简答方法就是,将 2,3,4 存 起来。而题目又说了,只能使用栈这种数据结构,那么我们考虑使用一个额外的栈来存放弹出的 2,3,4。
(pop 出来不扔掉,而是存起来)
整个过程类似这样:
比如,这个时候,我们想 push 一个 5,那么大概就是这样的:
然而这一过程,我们也可以发生在 push 阶段。
总之,就是我们需要在 push 或者 pop 的时候,将数组在两个栈之间倒腾一次。
# 关键点
- 在 push 的时候利用辅助栈(双栈)
# 代码
- 语言支持:JS, Python, Java
Javascript Code:
/* * @lc app=leetcode id=232 lang=javascript * * [232] Implement Queue using Stacks */ /** * Initialize your data structure here. */ var MyQueue = function() { // tag: queue stack array this.stack = []; this.helperStack = []; }; /** * Push element x to the back of queue. * @param {number} x * @return {void} */ MyQueue.prototype.push = function(x) { let cur = null; while ((cur = this.stack.pop())) { this.helperStack.push(cur); } this.helperStack.push(x); while ((cur = this.helperStack.pop())) { this.stack.push(cur); } }; /** * Removes the element from in front of queue and returns that element. * @return {number} */ MyQueue.prototype.pop = function() { return this.stack.pop(); }; /** * Get the front element. * @return {number} */ MyQueue.prototype.peek = function() { return this.stack[this.stack.length - 1]; }; /** * Returns whether the queue is empty. * @return {boolean} */ MyQueue.prototype.empty = function() { return this.stack.length === 0; }; /** * Your MyQueue object will be instantiated and called as such: * var obj = new MyQueue() * obj.push(x) * var param_2 = obj.pop() * var param_3 = obj.peek() * var param_4 = obj.empty() */
Python Code:
class MyQueue: def __init__(self): """ Initialize your data structure here. """ self.stack = [] self.help_stack = [] def push(self, x: int) -> None: """ Push element x to the back of queue. """ while self.stack: self.help_stack.append(self.stack.pop()) self.help_stack.append(x) while self.help_stack: self.stack.append(self.help_stack.pop()) def pop(self) -> int: """ Removes the element from in front of queue and returns that element. """ return self.stack.pop() def peek(self) -> int: """ Get the front element. """ return self.stack[-1] def empty(self) -> bool: """ Returns whether the queue is empty. """ return not bool(self.stack) # Your MyQueue object will be instantiated and called as such: # obj = MyQueue() # obj.push(x) # param_2 = obj.pop() # param_3 = obj.peek() # param_4 = obj.empty()
Java Code
class MyQueue { Stack<Integer> pushStack = new Stack<> (); Stack<Integer> popStack = new Stack<> (); /** Initialize your data structure here. */ public MyQueue() { } /** Push element x to the back of queue. */ public void push(int x) { while (!popStack.isEmpty()) { pushStack.push(popStack.pop()); } pushStack.push(x); } /** Removes the element from in front of queue and returns that element. */ public int pop() { while (!pushStack.isEmpty()) { popStack.push(pushStack.pop()); } return popStack.pop(); } /** Get the front element. */ public int peek() { while (!pushStack.isEmpty()) { popStack.push(pushStack.pop()); } return popStack.peek(); } /** Returns whether the queue is empty. */ public boolean empty() { return pushStack.isEmpty() && popStack.isEmpty(); } } /** * Your MyQueue object will be instantiated and called as such: * MyQueue obj = new MyQueue(); * obj.push(x); * int param_2 = obj.pop(); * int param_3 = obj.peek(); * boolean param_4 = obj.empty(); */
复杂度分析
- 时间复杂度:$O(N)$,其中 N 为 栈中元素个数,因为每次我们都要倒腾一次。
- 空间复杂度:$O(N)$,其中 N 为 栈中元素个数,多使用了一个辅助栈,这个辅助栈的大小和原栈的大小一样。
# 扩展
- 类似的题目有用队列实现栈,思路是完全一样的,大家有兴趣可以试一下。
- 栈混洗也是借助另外一个栈来完成的,从这点来看,两者有相似之处。
# 延伸阅读
实际上现实中也有使用两个栈来实现队列的情况,那么为什么我们要用两个 stack 来实现一个 queue?
其实使用两个栈来替代一个队列的实现是为了在多进程中分开对同一个队列对读写操作。一个栈是用来读的,另一个是用来写的。当且仅当读栈满时或者写栈为空时,读写操作才会发生冲突。
当只有一个线程对栈进行读写操作的时候,总有一个栈是空的。在多线程应用中,如果我们只有一个队列,为了线程安全,我们在读或者写队列的时候都需要锁住整个队列。而在两个栈的实现中,只要写入栈不为空,那么
push
操作的锁就不会影响到pop
。