Implement a last-in-first-out (LIFO) stack using only two queues. The implemented stack should support all the functions of a normal stack (push
, top
, pop
, and empty
).
Implement the MyStack
class:
void push(int x)
: Pushes element x
to the top of the stack.int pop()
: Removes the element on the top of the stack and returns it.int top()
: Returns the element on the top of the stack.boolean empty()
: Returns true
if the stack is empty, false
otherwise.Notes:
push to back
, peek/pop from front
, size
and is empty
operations are valid.For example:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // return 2
myStack.pop(); // return 2
myStack.empty(); // return False
Can you implement this stack using only standard queue operations?
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 need to create a stack (like a pile of plates) using only queues (like lines where the first person in line is served first). The brute force approach essentially uses one queue to store the stack and, for every new element, shuffles all existing elements to make room for the new one at the 'top'.
Here's how the algorithm would work step-by-step:
from collections import deque
class StackUsingQueues:
def __init__(self):
self.queue = deque()
def push(self, element):
self.queue.append(element)
# Rotate the queue to place the new element at the front
for _ in range(len(self.queue) - 1):
self.queue.append(self.queue.popleft())
def pop(self):
if not self.is_empty():
return self.queue.popleft()
def top(self):
# Return the front element
if not self.is_empty():
return self.queue[0]
def is_empty(self):
# Easy check to see if the stack is empty.
return len(self.queue) == 0
The key idea is to use two queues to simulate the stack's behavior. We'll make one of the queue do most of the work so pushing items will be as simple as adding to a queue, while popping will require shifting elements between the queues to maintain the stack's last-in, first-out order.
Here's how the algorithm would work step-by-step:
class MyStack:
def __init__(self):
self.queue_one = []
self.queue_two = []
def push(self, item_to_push: int) -> None:
self.queue_one.append(item_to_push)
def pop(self) -> int:
# Move all but the last element
while len(self.queue_one) > 1:
self.queue_two.append(self.queue_one.pop(0))
# This is the top of the stack
top_of_stack = self.queue_one.pop(0)
self.queue_one, self.queue_two = self.queue_two, self.queue_one
return top_of_stack
def top(self) -> int:
# Move all but the last element
while len(self.queue_one) > 1:
self.queue_two.append(self.queue_one.pop(0))
# Get the top element
top_of_stack = self.queue_one[0]
self.queue_two.append(self.queue_one.pop(0))
self.queue_one, self.queue_two = self.queue_two, self.queue_one
# Return the top element
return top_of_stack
def empty(self) -> bool:
# Stack is empty when queue is empty
return len(self.queue_one) == 0
Case | How to Handle |
---|---|
Calling pop or top on an empty stack | Raise an exception (e.g., EmptyStackException) or return a specific error value (like null) indicating an invalid operation. |
Large number of push operations causing queue overflow (potential memory issue) | Ensure that the queue implementation can dynamically resize or has a maximum size with appropriate error handling to prevent memory exhaustion. |
Negative or zero values being pushed onto the stack | The implementation should handle these values correctly as they are valid integers; if the problem dictates only positives, then validation must be added. |
Very large integer values being pushed (potential overflow issues depending on language) | Use appropriate data types (e.g., long) to handle large integers or check for potential overflows before pushing. |
Concurrent access from multiple threads | Implement thread safety using locks or synchronized queues to prevent race conditions. |
Testing the stack after a mix of push, pop, and top operations | Create comprehensive test cases covering various sequences of operations to ensure correct behavior under different usage patterns. |
Extreme edge cases where the queues are constantly re-arranged with push and pop actions | Test with scenarios that alternate between pushing and popping multiple times to ensure the algorithm maintains stack order |
If queues use linked lists internally, confirm they are not susceptible to memory leaks after many push/pop cycles | In languages with manual memory management, ensure all allocated memory is properly deallocated during pop operations to prevent memory leaks. |