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()
: Returns true
if the queue is empty, false
otherwise.Constraints:
1 <= x <= 9
push
, pop
, peek
, and empty
.pop
and peek
are valid.For example:
Input
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
Output
[null, null, null, 1, 1, false]
Explanation
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
Can you implement the queue such that each operation is amortized O(1)
time complexity? In other words, performing n
operations will take overall O(n)
time even if one of those operations may take longer.
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
We want to simulate a waiting line (queue) using only two stacks. The straightforward but less efficient way is to move all elements from one stack to the other whenever we want to add or remove something from our queue.
Here's how the algorithm would work step-by-step:
class MyQueue:
def __init__(self):
self.stack_one = []
self.stack_two = []
def push(self, element):
# Moving elements to stack two to make space for new element
while self.stack_one:
self.stack_two.append(self.stack_one.pop())
self.stack_one.append(element)
# Moving back elements to stack one
while self.stack_two:
self.stack_one.append(self.stack_two.pop())
def pop(self):
# Stack one represents our queue, so pop the top
if self.stack_one:
return self.stack_one.pop()
return None
def peek(self):
# Return top of stack one to simulate queue
if self.stack_one:
return self.stack_one[-1]
return None
def empty(self):
# Queue empty when stack one is empty
return not self.stack_one
We'll use two stacks to simulate a queue. The main idea is that one stack handles adding new elements, and the other handles removing them, transferring elements only when necessary to maintain queue order.
Here's how the algorithm would work step-by-step:
class MyQueue:
def __init__(self):
self.input_stack = []
self.output_stack = []
def push(self, element):
self.input_stack.append(element)
def pop(self):
# Transfer elements if output stack is empty.
if not self.output_stack:
while self.input_stack:
self.output_stack.append(self.input_stack.pop())
if self.output_stack:
return self.output_stack.pop()
else:
return None
def peek(self):
# Transfer elements only when output stack is empty.
if not self.output_stack:
while self.input_stack:
self.output_stack.append(self.input_stack.pop())
if self.output_stack:
return self.output_stack[-1]
else:
return None
def empty(self):
# Queue is empty when both stacks are empty.
return not self.input_stack and not self.output_stack
Case | How to Handle |
---|---|
Multiple pop operations when queue is empty. | Raise an exception or return null/error code to indicate an invalid operation on an empty queue. |
Large number of push operations to test memory usage. | The stack operations should scale with available memory, but monitoring is important for resource leaks. |
Simultaneous push and pop operations from multiple threads (concurrency). | Implement thread-safe stacks using locks or atomic operations to ensure correct behavior. |
Pushing null values into the queue. | Decide if null values are allowed. If not, reject them and throw an exception, otherwise handle them as a valid value. |
Check the correct order when pushing and immediately popping one element. | Verify that the single pushed element is returned by the immediate pop operation. |
Interleaving push and pop operations to test FIFO behavior | Ensure that pop always returns the earliest pushed element even with frequent pushes and pops. |
Negative or zero sized inputs | The size operation should always return a non-negative integer, and the queue operations should work correctly even when the queue is logically empty (size is zero). |
Integer overflow with a huge number of elements pushed to the queue. | Consider the maximum possible stack size and handle integer overflow exceptions if the number of elements exceeds the limit. |