- Implement Stack using Queues
使用队列实现堆栈的以下操作:
- push(x) --Push element x onto stack.(元素x入栈)
- pop() – Removes the element on top of the stack.(删除堆栈顶部的元素)
- top() – Get the top element.(获取顶部元素)
- empty() – Return whether the stack is empty.(返回堆栈是否为空)
Example:(例子)
MyStack stack = new MyStack();
stack.push(1);
stack.push(2);
stack.top(); // returns 2
stack.pop(); // returns 2
stack.empty(); // returns false
Notes:(注意)
- 您必须仅使用队列的标准操作(standard operations of a queue) - 这意味着只能向后推(only push to back),从前面查看/弹出(peek/pop from front),大小,并且空操作是有效的。
- 只要您只使用队列的标准操作,就可以使用list或deque(双端队列)来模拟队列。
- 您可以假设所有操作都是有效的(例如,不会在空堆栈上调用pop或top操作)。
方法1 (两个队列, push - O(1), pop O(n))
Stack is LIFO (last in - first out) data structure, in which elements are added and removed from the same end, called top. In general stack is implemented using array or linked list, but in the current article we will review a different approach for implementing stack using queues. In contrast queue is FIFO (first in - first out) data structure, in which elements are added only from the one side - rear and removed from the other - front. In order to implement stack using queues, we need to maintain two queues q1 and q2. Also we will keep top stack element in a constant memory.
Stack是LIFO(后进先出)数据结构,其中元素在同一端添加和删除,称为top。 通常,堆栈是使用数组(array)或链表(linked list)实现的,但在本文中,我们将回顾(review)使用队列实现堆栈的不同方法。 相反,队列是FIFO(先进先出)数据结构,其中元素仅从一侧添加 - 后部 - 从另一侧 - 从前面移除。 为了使用队列实现堆栈,我们需要维护两个队列q1和q2。 此外,我们将顶部堆栈元素保持在恒定的内存中(constant memory)。
Push操作
新元素始终添加到队列q1的后面(rear),并保持为顶部(top)堆栈元素
Java
private Queue<Integer> q1 = new LinkedList<>();
private Queue<Integer> q2 = new LinkedList<>();
private int top;
// Push element x onto stack.
public void push(int x) {
q1.add(x);
top = x;
}
Complexity Analysis(复杂度分析)
- 时间复杂度 : O(1). 队列实现为链表,添加操作具有O(1)时间复杂度。
- 空间复杂度 : O(1)
我们需要从堆栈顶部删除元素。 这是q1中最后插入的元素。 因为队列是FIFO(先进先出)数据结构,所以只有在删除除了它之外的所有元素之后才能删除最后插入的元素。 出于这个原因,我们需要维护额外的队列q2,它将用作临时存储(temporary storage),以便从q1中排除已删除的元素。 q2中最后插入的元素保持为顶部。 然后算法删除q1中的最后一个元素。 我们将q1与q2交换以避免将所有元素从q2复制到q1。
// Removes the element on top of the stack.
public void pop() {
while (q1.size() > 1) {
top = q1.remove();
q2.add(top);
}
q1.remove();
Queue<Integer> temp = q1;
q1 = q2;
q2 = temp;
}
Complexity Analysis(复杂度分析):
- 时间复杂度:O(n)。该算法将n个元素从q1中取出,并将n-1个元素进入q2,其中n是堆栈大小。 这给出了2n-1次操作。
- 空间复杂度:O(1)。
方法2(两个队列,push - O(n), pop - O(1))
Push操作
该算法将每个新元素插入队列q2并将其保留为顶部元素。 如果队列q1不为空(堆栈中有元素),我们从q1中删除所有元素并将它们添加到q2。 这样,新插入的元素(堆栈中的顶部元素)将始终位于q2的前面。 我们将q1与q2交换以避免将所有元素从q2复制到q1。
Java
public void push(int x) {
q2.add(x);
top = x;
while (!q1.isEmpty()) {
q2.add(q1.remove());
}
Queue<Integer> temp = q1;
q1 = q2;
q2 = temp;
}
Complexity Analysis(复杂度分析)
- 时间复杂度:O(n)。 该算法从q1中删除n个元素,并将n + 1个元素插入q2,其中n是堆栈大小。 这给出了2n + 1次操作。 链接列表中添加和删除的操作具有O(1)复杂度。
- 空间复杂度 : O(1)。
Pop操作
该算法使队列q1中的元素出列,并将q1的前元素保持为顶部。
Figure 4. Pop an element from stack
Java
// Removes the element on top of the stack.
public void pop() {
q1.remove();
if (!q1.isEmpty()) {
top = q1.peek();
}
}
Complexity Analysis
- 时间复杂度:O(1)
- 空间复杂度:O(1)
在这两种方法中,空操作和顶部操作具有相同的实现。
Empty
队列q1始终包含所有堆栈元素,因此如果堆栈为空,算法会检查q1大小以返回。
// Return whether the stack is empty.
public boolean empty() {
return q1.isEmpty();
}
时间复杂度 : O(1).
空间复杂度 : O(1).
Top
top元素保存在固定内存(constant memory)中,每次进入或弹出元素时都会修改。
// Get the top element.
public int top() {
return top;
}
- 时间复杂度 : O(1). 顶部元素已提前计算,仅在顶部操作中返回。
- 空间复杂度 : O(1).
方法3 (一个队列, push - O(n), pop O(1) )
上面提到的两种方法都有一个缺点,它们使用两个队列。 这可以优化,因为我们只使用一个队列,而不是两个队列。
Push
当我们将一个元素推入队列时,由于队列的属性,它将被存储在队列的后面。 但是我们需要实现一个堆栈,其中最后插入的元素应该在队列的前面,而不是在后面。 为了实现这一点,我们可以在推送新元素时反转队列元素的顺序。
Figure 5. Push an element in stack
java
private LinkedList q1 = new LinkedList<>();
// Push element x onto stack.
public void push(int x) {
q1.add(x);
int sz = q1.size();
while (sz > 1) {
q1.add(q1.remove());
sz--;
}
}
Complexity Analysis(复杂度分析)
- 时间复杂度 : O(n). 该算法删除n个元素并将n + 1个元素插入q1,其中n是堆栈大小。 这给出了2n + 1次操作。链接列表中添加和删除的操作具有O(1)复杂性
- 空间复杂度:O(1)
Pop
最后插入的元素总是存储在q1的前面,我们可以将它弹出一段时间
java
// Removes the element on top of the stack.
public void pop() {
q1.remove();
}
复杂度分析
- 时间复杂度: O(1)O(1).
- 空间复杂度 : O(1)O(1)
Empty
队列q1包含所有堆栈元素,因此算法检查q1是否为空。
// Return whether the stack is empty.
public boolean empty() {
return q1.isEmpty();
}
时间复杂度 : O(1).
空间复杂度 : O(1).
Top
顶部元素始终位于q1的前部。 算法返回它。
// Get the top element.
public int top() {
return q1.peek();
}
时间复杂度 : O(1).
空间复杂度 : O(1).
其他实现
C++: 0 ms
class Stack {
queue<int> q;
public:
void push(int x) {
q.push(x);
for (int i=1; i<q.size(); i++) {
q.push(q.front());
q.pop();
}
}
void pop() {
q.pop();
}
int top() {
return q.front();
}
bool empty() {
return q.empty();
}
};
Java: 140 ms
class MyStack {
private Queue<Integer> queue = new LinkedList<>();
public void push(int x) {
queue.add(x);
for (int i=1; i<queue.size(); i++)
queue.add(queue.remove());
}
public void pop() {
queue.remove();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
Python: 36 ms
class Stack:
def __init__(self):
self._queue = collections.deque()
def push(self, x):
q = self._queue
q.append(x)
for _ in range(len(q) - 1):
q.append(q.popleft())
def pop(self):
return self._queue.popleft()
def top(self):
return self._queue[0]
def empty(self):
return not len(self._queue)