225. Implement Stack using Queues
Problem Description
The goal of this problem is to design a stack data structure with a Last-In-First-Out (LIFO) behavior using only two queues to store the data. This means that we need to implement the usual stack operations (push
, top
, pop
, and empty
) but with a constraint that only the standard queue operations (enqueue/push to back, dequeue/pop from front, check size, and check if empty) are allowed. We're essentially required to emulate the behavior of one data structure (stack) with another (queue).
A stack traditionally allows adding (pushing) an item on top, removing (popping) the top item, inspecting (top) the item at the top, and checking if the stack is empty. Instead of using the stack's native methods, we have to use the properties of two queues to achieve the same outcomes.
Here's the specific contract for the MyStack
class:
void push(int x)
- Adds the elementx
to the top of the stack.int pop()
- Removes and returns the element from the top of the stack.int top()
- Returns the element from the top of the stack without removing it.boolean empty()
- Returnstrue
if the stack is empty, otherwise returnsfalse
.
Intuition
The major challenge is that the queue is a First-In-First-Out (FIFO) structure, whereas a stack is a Last-In-First-Out (LIFO) structure. So, our solution must invert the order of queue elements with each push operation to maintain the LIFO order of a stack.
To implement a stack using queues, the push
operation will involve adding the new element to an empty auxiliary queue, and then dequeuing all elements from the primary queue and enqueueing them to the auxiliary queue, which effectively places the new element at the beginning of the queue that represents our stack. Next, we swap the roles of the two queues: the auxiliary queue becomes the primary queue (our stack), and the old primary queue becomes empty and hence becomes the new auxiliary queue. The pop
operation simply dequeues from the primary queue.
By doing this, the front of the primary queue always has the last pushed element, which enables us to do a pop
and top
operation as if it were a stack.
The empty
operation checks whether the primary queue is empty, which indicates whether the stack is empty.
It's important to note that the key to this solution is the swapping mechanism after each push - it ensures the primary queue always has the last pushed element ready to be popped, thus fulfilling the stack's LIFO property using the queue's FIFO order.
Solution Approach
In the reference solution approach, we see the implementation of a stack using two double-ended queues (deque()
in Python), which allows us to achieve our goal of simulating a stack using queue operations. Here is how each method of the MyStack
class works under the hood:
push(int x)
- Create a temporary queue
q2
and enqueue the new elementx
intoq2
. - Dequeue all elements from the existing queue
q1
and enqueue them intoq2
. This ensures that the new elementx
ends up at the front ofq2
, maintaining the stack's LIFO order. - Now, swap
q1
andq2
, so thatq1
becomes the queue with the new element at the front (acting as the "top" of the stack) andq2
becomes empty, ready to be used for the nextpush
operation.
pop()
- Directly dequeue from
q1
, which removes and returns the front element ofq1
, the element that was most recently pushed and thus on the "top" of the stack.
top()
- Access the front element of
q1
directly, which is the element on the "top" of the stack, and return it. This operation does not remove the element from the queue.
empty()
- Return a boolean value indicating whether
q1
is empty. Ifq1
is empty, that means there are no elements in the stack.
Let's break down the core algorithm pattern used here:
- Two Queue Stack Simulation: We simulate stack operations using two queues by inverting the queue each time we push an element onto the "stack". The reversal of the queue order with every
push
operation is critical to simulate the stack's LIFO property. - Swap Mechanism: We make use of two queues and consistently keep one queue empty by swapping them after each push operation. This effectively ensures a fresh auxiliary queue for the next push operation and maintains the stack order in the primary queue.
- Queue Operations: Standard queue operations are
enqueue
(to add elements),dequeue
(to remove elements), and checking if the queue isempty
. We use these operations in our solution to emulate the corresponding stack operationspush
,pop
, andempty
.
In summary, the stack is simulated using two queues by ensuring the most recently pushed element is always at the front of the primary queue. This is done by reversing the order of elements with every push operation, which is achieved by moving all elements from one queue to the other, followed by a swap.
The algorithms and patterns used in this approach are straightforward, yet clever, leveraging the basic queue operations to fulfill the requirements of a completely different abstract data type, the stack.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution with a small example:
Suppose we perform the following operations on an initially empty MyStack
object:
push(1)
push(2)
top()
pop()
empty()
Now let's walk through these operations step by step using the described algorithm:
Operation 1: push(1)
-
Start with two empty queues,
q1
andq2
. -
q1
: [] -
q2
: [] -
Enqueue
1
toq2
. -
q2
: [1] -
Since
q1
is empty, no elements are dequeued fromq1
and enqueued toq2
. -
Swap the roles of
q1
andq2
. Nowq1
is our stack with1
in it. -
q1
: [1] -
q2
: []
Operation 2: push(2)
-
Enqueue
2
toq2
. -
q2
: [2] -
Dequeue
1
fromq1
and enqueue it toq2
. This will put2
in front of1
, which maintains our stack's LIFO order. -
q2
: [2, 1] -
q1
: [] -
Swap the roles of
q1
andq2
again.q1
is now effectively the stack. -
q1
: [2, 1] -
q2
: []
Operation 3: top()
- Return the front element of
q1
, which is2
. This does not alter the queue.
Operation 4: pop()
- Dequeue from
q1
, removing the front element2
, which is the "top" of the stack. q1
: [1]
Operation 5: empty()
- Check if
q1
is empty. It's not, as it contains1
, so returnfalse
.
At the end of these operations, our MyStack
object has successfully emulated standard stack behavior with the help of two queues, demonstrating the key principles of this problem's solution approach.
Solution Implementation
1from collections import deque
2
3class MyStack:
4 def __init__(self):
5 # Use two queues to implement the stack behavior
6 self.main_queue = deque()
7 self.helper_queue = deque()
8
9 def push(self, x: int) -> None:
10 # Always push the new element to the helper_queue
11 self.helper_queue.append(x)
12 # Transfer all existing elements from the main_queue to the helper_queue
13 while self.main_queue:
14 self.helper_queue.append(self.main_queue.popleft())
15 # Swap the references of the two queues to make helper_queue the new main_queue
16 self.main_queue, self.helper_queue = self.helper_queue, self.main_queue
17
18 def pop(self) -> int:
19 # Remove and return the last pushed element which is at the start of the main_queue
20 return self.main_queue.popleft()
21
22 def top(self) -> int:
23 # Return the last pushed element without removing it, which is at the start of the main_queue
24 return self.main_queue[0]
25
26 def empty(self) -> bool:
27 # Check whether the main_queue is empty or not
28 return len(self.main_queue) == 0
29
30# Usage example
31# stack = MyStack()
32# stack.push(1)
33# stack.push(2)
34# print(stack.top()) # Returns 2
35# print(stack.pop()) # Returns 2
36# print(stack.empty()) # Returns False
37
1import java.util.ArrayDeque;
2import java.util.Deque;
3
4class MyStack {
5 private Deque<Integer> mainQueue = new ArrayDeque<>();
6 private Deque<Integer> auxQueue = new ArrayDeque<>();
7
8 // Constructor for the stack
9 public MyStack() {
10 // No initialization needed as Java Deque is initialized by default
11 }
12
13 // Method to push an element into the stack
14 public void push(int x) {
15 // Add the element to the auxiliary queue
16 auxQueue.offer(x);
17
18 // Move all elements from the main queue to the auxiliary queue, reversing the order
19 while (!mainQueue.isEmpty()) {
20 auxQueue.offer(mainQueue.poll());
21 }
22
23 // Swap the main and auxiliary queues
24 Deque<Integer> temp = mainQueue;
25 mainQueue = auxQueue;
26 auxQueue = temp;
27 }
28
29 // Method to pop an element from the top of the stack
30 public int pop() {
31 // Remove and return the front element from the main queue
32 return mainQueue.poll();
33 }
34
35 // Method to get the top element of the stack
36 public int top() {
37 // Get the front element from the main queue without removing it
38 return mainQueue.peek();
39 }
40
41 // Method to check if the stack is empty
42 public boolean empty() {
43 // Return true if the main queue is empty
44 return mainQueue.isEmpty();
45 }
46}
47
48// Class usage:
49// MyStack stack = new MyStack();
50// stack.push(1);
51// int popped = stack.pop();
52// int topElement = stack.top();
53// boolean isEmpty = stack.empty();
54
1#include <queue>
2using namespace std;
3
4class MyStack {
5public:
6 // Constructor
7 MyStack() {
8 // No initialization required as we're using STL queue
9 }
10
11 // Push element x onto stack
12 void push(int x) {
13 temporaryQueue.push(x);
14
15 // Reverse the order of elements by moving everything from mainQueue to temporaryQueue
16 while (!mainQueue.empty()) {
17 temporaryQueue.push(mainQueue.front());
18 mainQueue.pop();
19 }
20
21 // Swap the names of mainQueue and temporaryQueue to reflect the change
22 swap(mainQueue, temporaryQueue);
23 }
24
25 // Removes the element on top of the stack and returns that element
26 int pop() {
27 int topElement = mainQueue.front(); // Get the top element
28 mainQueue.pop(); // Remove the top element from the queue
29 return topElement; // Return the popped element
30 }
31
32 // Get the top element of the stack
33 int top() {
34 return mainQueue.front(); // The front of mainQueue is the top of stack
35 }
36
37 // Returns true if the stack is empty, false otherwise
38 bool empty() {
39 return mainQueue.empty(); // Check if the mainQueue is empty
40 }
41
42private:
43 queue<int> mainQueue; // This will act as the stack
44 queue<int> temporaryQueue; // Used to reverse the order of elements in push operation
45};
46
47/**
48 * Your MyStack object will be instantiated and called as such:
49 * MyStack* obj = new MyStack();
50 * obj->push(x);
51 * int param_2 = obj->pop();
52 * int param_3 = obj->top();
53 * bool param_4 = obj->empty();
54 */
55
1// Initialization of two queues to simulate a stack
2let primaryQueue: number[] = [];
3let secondaryQueue: number[] = [];
4
5/**
6 * Simulates pushing an element onto stack using queues.
7 * @param {number} x - The element to push onto the stack.
8 */
9function push(x: number): void {
10 // Push the element into the secondary queue
11 secondaryQueue.push(x);
12
13 // Move all elements from the primary queue to the secondary queue
14 while (primaryQueue.length) {
15 secondaryQueue.push(primaryQueue.shift()!);
16 }
17
18 // Swap the names of the queues, making the secondary queue the new primary queue
19 [primaryQueue, secondaryQueue] = [secondaryQueue, primaryQueue];
20}
21
22/**
23 * Simulates popping an element from the stack.
24 * @return {number} - The element popped from the stack.
25 */
26function pop(): number {
27 // Remove the element from the front of the primary queue
28 return primaryQueue.shift()!;
29}
30
31/**
32 * Retrieves the element at the top of the stack without removing it.
33 * @return {number} - The element at the top of the stack.
34 */
35function top(): number {
36 // Return the front element of the primary queue
37 return primaryQueue[0];
38}
39
40/**
41 * Checks if the stack is empty.
42 * @return {boolean} - True if the stack is empty, false otherwise.
43 */
44function empty(): boolean {
45 // Check if the primary queue is empty
46 return primaryQueue.length === 0;
47}
48
49// Example usage:
50// push(1);
51// const elementPopped = pop();
52// const elementAtTop = top();
53// const isStackEmpty = empty();
54
Time and Space Complexity
The given Python code defines a class MyStack
which implements a stack using two deques (self.q1
and self.q2
). Here's the complexity analysis for each method in the MyStack
class:
Time Complexity:
-
push
method:- Each
push
operation requires moving all elements fromq1
toq2
and then swapping the names ofq1
andq2
. - If
n
is the number of elements in the stack, the while loop runs forn
iterations as it movesn
elements fromq1
toq2
. - Therefore, the time complexity for
push
isO(n)
.
- Each
-
pop
method:- The
pop
operation removes an element from the front ofq1
. - Since deque operations for popping from the front are
O(1)
, the time complexity forpop
isO(1)
.
- The
-
top
method:- The
top
operation retrieves an element from the front ofq1
without removing it. - Accessing the front element is
O(1)
for a deque, so the time complexity fortop
isO(1)
.
- The
-
empty
method:- The
empty
operation checks whetherq1
is empty by comparing its length to 0. - Checking if the deque is empty is
O(1)
, hence the time complexity forempty
isO(1)
.
- The
Space Complexity:
- The class uses two deques to store the elements of the stack.
- The space complexity is dependent on the number of elements present in the stack.
- If the stack contains
n
elements, the space complexity isO(n)
since we need to store alln
elements.
In conclusion, the push
method has a time complexity of O(n)
, while pop
, top
, and empty
methods have a constant time complexity of O(1)
. The space complexity for the entire MyStack
class is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!