Implement Queue using Stacks
Problem Description
Implement a first-in-first-out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (push, peek, pop, and empty).
Implement the MyQueue class:
void push(int x)Pushes element x to the back of the queue.int pop()Removes the element from the front of the queue and returns it.int peek()Returns the element at the front of the queue.boolean empty()Returnstrueif the queue is empty,falseotherwise.
Notes:
- You must use only standard operations of a stack, which means only
push to top,peek/pop from top,size, andis emptyoperations are valid.
Examples
Example 1:Input: [“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”] [[], [1], [2], [], [], []] Output: [null, null, null, 1, 1, false]
Constraints
1 <= x <= 9- At most
100calls will be made topush,pop,peek, andempty. - All the calls to
popandpeekare valid.
Inversion Stack with Amortized Cost
To implement a FIFO queue using two stacks, we split operations into input and output stacks.
- When pushing an element, push it directly onto the
inputstack. - When popping or peeking, if the
outputstack is empty, pop all elements from theinputstack and push them onto theoutputstack. This reverses their order, bringing the oldest elements to the top. - Pop or peek from the
outputstack. This amortizes the time complexity of all operations to O(1), since each element is pushed and popped a fixed number of times.
Solution 1: Input and Output Stacks
Divide operations between an input stack and a lazy output inversion stack.
import java.util.Stack;
class MyQueue {
private Stack<Integer> input;
private Stack<Integer> output;
public MyQueue() {
input = new Stack<>();
output = new Stack<>();
}
public void push(int x) {
input.push(x);
}
public int pop() {
peek(); // ensure output stack is populated
return output.pop();
}
public int peek() {
if (output.isEmpty()) {
while (!input.isEmpty()) {
output.push(input.pop());
}
}
return output.peek();
}
public boolean empty() {
return input.isEmpty() && output.isEmpty();
}
}class MyQueue:
def __init__(self):
self.input = []
self.output = []
def push(self, x: int) -> None:
self.input.append(x)
def pop(self) -> int:
self.peek() # ensure output stack is populated
return self.output.pop()
def peek(self) -> int:
if not self.output:
while self.input:
self.output.append(self.input.pop())
return self.output[-1]
def empty(self) -> bool:
return not self.input and not self.output#include <stack>
class MyQueue {
private:
std::stack<int> input;
std::stack<int> output;
public:
MyQueue() {}
void push(int x) {
input.push(x);
}
int pop() {
peek(); // ensure output stack is populated
int val = output.top();
output.pop();
return val;
}
int peek() {
if (output.empty()) {
while (!input.empty()) {
output.push(input.top());
input.pop();
}
}
return output.top();
}
bool empty() {
return input.empty() && output.empty();
}
};Complexity Analysis
- Time Complexity:
pushandemptytake O(1) time.popandpeektake O(1) amortized time, since elements are moved between stacks at most once. - Space Complexity: O(n) auxiliary space to store elements in the two stacks.
Where It Breaks
If we perform a sequence that repeatedly alternates between pushing and popping from an empty output stack with single items, it doesn’t break the amortized bounds because each item only shifts once.
Common Mistakes
- Incorrect inversion timing: Inverting the input stack on every push operation, which destroys the FIFO ordering and degrades performance to O(n) per push.
- Forgetting to check both stacks: Checking only one stack in the
emptycheck, which ignores elements stored in the other stack.
Frequently Asked Questions
Why is pop/peek amortized O(1)? An element is pushed to input stack once, popped from input stack once, pushed to output stack once, and popped from output stack once. Across the lifespan of the queue, the total operations per element is 4, which averages out to O(1) per call.
Can we implement this with only one stack? No, a single stack cannot reverse elements to achieve FIFO behavior without allocating recursive calls on the runtime stack, which still uses O(n) stack memory.