Introduction
The Stack Using Two Queues problem is a well-known coding challenge. The goal is to implement the basic operations of a stack—push()
, pop()
, peek()
, and size()
—using two queues. This challenge demonstrates a fundamental understanding of how data structures like stacks and queues can be manipulated to achieve different functionalities.
A stack follows the Last In, First Out (LIFO) principle, whereas a queue follows the First In, First Out (FIFO) principle. By using two queues, we can simulate the behavior of a stack while maintaining queue-based operations.
Problem Statement
You are required to implement a stack using two queues with the following operations:
- push(x): Add an element to the top of the stack.
- pop(): Remove and return the top element from the stack.
- peek(): Get the top element of the stack without removing it.
- size(): Return the number of elements in the stack.
Example:
Input:
push(1)
push(2)
peek()
→ returns2
pop()
→ returns2
isEmpty()
→ returnsfalse
Output:
- The last element pushed is
2
, so bothpeek()
andpop()
return2
. The stack still contains1
, soisEmpty()
returnsfalse
.
- The last element pushed is
Approach
Using Two Queues
To implement a stack using two queues, we maintain two queues:
- Queue 1: Main queue for holding elements.
- Queue 2: Auxiliary queue to help with reordering elements during
pop()
andpeek()
operations.
Key Operations:
- push(x): Push the element to the currently active queue.
- pop(): Transfer all but the last element from the active queue to the other queue, then remove and return the last element.
- peek(): Similar to
pop()
, but instead of removing the last element, return it. - size(): Return the current size of the stack.
Java Code Implementation
MyStackViaQueue Class
import java.util.ArrayDeque;
import java.util.EmptyStackException;
import java.util.Queue;
public class MyStackViaQueue<E> {
private final Queue<E> queue1; // Primary queue
private final Queue<E> queue2; // Auxiliary queue
private E peek; // Holds the top element for efficient peek
private int size; // Holds the current size of the stack
// Constructor to initialize the queues
public MyStackViaQueue() {
queue1 = new ArrayDeque<>();
queue2 = new ArrayDeque<>();
}
// Push operation: Add an element to the stack
public void push(E e) {
if (!queue1.isEmpty()) {
if (peek != null) {
queue1.add(peek);
}
queue1.add(e);
} else {
if (peek != null) {
queue2.add(peek);
}
queue2.add(e);
}
size++;
peek = null; // Reset peek
}
// Pop operation: Remove and return the top element from the stack
public E pop() {
if (size() == 0) {
throw new EmptyStackException();
}
if (peek != null) {
E e = peek;
peek = null;
size--;
return e;
}
E e;
if (!queue1.isEmpty()) {
e = switchQueue(queue1, queue2);
} else {
e = switchQueue(queue2, queue1);
}
size--;
return e;
}
// Peek operation: Return the top element without removing it
public E peek() {
if (size() == 0) {
throw new EmptyStackException();
}
if (peek == null) {
if (!queue1.isEmpty()) {
peek = switchQueue(queue1, queue2);
} else {
peek = switchQueue(queue2, queue1);
}
}
return peek;
}
// Return the size of the stack
public int size() {
return size;
}
// Helper method to switch elements between queues
private E switchQueue(Queue<E> from, Queue<E> to) {
while (from.size() > 1) {
to.add(from.poll());
}
return from.poll(); // Return the last element (top of the stack)
}
}
Explanation
- push(x):
- The element is added to whichever queue (either
queue1
orqueue2
) is currently active. - The
peek
is reset tonull
to ensure the correct element is retrieved duringpeek()
andpop()
operations.
- The element is added to whichever queue (either
public void push(E e) {
if (!queue1.isEmpty()) {
if (peek != null) {
queue1.add(peek);
}
queue1.add(e);
} else {
if (peek != null) {
queue2.add(peek);
}
queue2.add(e);
}
size++;
peek = null;
}
- pop():
- Transfer all elements from the active queue (either
queue1
orqueue2
) to the other queue, except for the last element. - The last element is then removed and returned as the result of the
pop()
operation.
- Transfer all elements from the active queue (either
public E pop() {
if (size() == 0) {
throw new EmptyStackException();
}
if (peek != null) {
E e = peek;
peek = null;
size--;
return e;
}
E e;
if (!queue1.isEmpty()) {
e = switchQueue(queue1, queue2);
} else {
e = switchQueue(queue2, queue1);
}
size--;
return e;
}
- peek():
- Similar to
pop()
, but instead of removing the last element, it stores the last element in thepeek
variable and returns it.
- Similar to
public E peek() {
if (size() == 0) {
throw new EmptyStackException();
}
if (peek == null) {
if (!queue1.isEmpty()) {
peek = switchQueue(queue1, queue2);
} else {
peek = switchQueue(queue2, queue1);
}
}
return peek;
}
- size():
- Returns the size of the stack, which is maintained as a variable and updated during each
push()
andpop()
operation.
- Returns the size of the stack, which is maintained as a variable and updated during each
public int size() {
return size;
}
Main Class to Test the Stack Implementation
package coding.challenge;
public class Main {
public static void main(String[] args) {
MyStackViaQueue<Integer> stack = new MyStackViaQueue<>();
stack.push(25);
stack.push(35);
stack.push(15);
System.out.println("Size: " + stack.size());
System.out.println("Peek: " + stack.peek());
System.out.println("Size: " + stack.size());
System.out.println("Pop: " + stack.pop());
System.out.println("Size: " + stack.size());
System.out.println("Peek: " + stack.peek());
System.out.println("Peek: " + stack.peek());
System.out.println("Size: " + stack.size());
System.out.println("Push 17");
stack.push(17);
System.out.println("Push 12");
stack.push(12);
System.out.println("Peek: " + stack.peek());
System.out.println("Peek: " + stack.peek());
System.out.println("Size: " + stack.size());
System.out.println("Pop: " + stack.pop());
System.out.println("Size: " + stack.size());
System.out.println("Pop: " + stack.pop());
System.out.println("Size: " + stack.size());
System.out.println("Push 55");
stack.push(55);
System.out.println("Size: " + stack.size());
System.out.println("Peek: " + stack.peek());
System.out.println("Peek: " + stack.peek());
System.out.println("Size: " + stack.size());
System.out.println("Pop: " + stack.pop());
System.out.println("Size: " + stack.size());
System.out.println("Pop: " + stack.pop());
System.out.println("Size: " + stack.size());
System.out.println("Peek: " + stack.peek());
System.out.println("Pop: " + stack.pop());
System.out.println("Size: " + stack.size());
}
}
Example Output:
Size: 3
Peek: 15
Size: 3
Pop: 15
Size: 2
Peek: 35
Peek: 35
Size: 2
Push 17
Push 12
Peek: 12
Peek: 12
Size: 4
Pop: 12
Size: 3
Pop: 17
Size: 2
Push 55
Size: 3
Peek: 55
Peek: 55
Size: 3
Pop: 55
Size: 2
Pop: 35
Size: 1
Peek: 25
Pop: 25
Size: 0
Time Complexity
- Push Operation: O(1), as elements are added to the active queue without moving elements.
- Pop Operation: O(n), where
n
is the number of elements in the stack. All elements are transferred from one queue to another duringpop()
. - Peek Operation: O(n), similar to
pop()
, as all elements are transferred. - Size Operation: O(1), as the size is tracked by a variable.
Space Complexity
- Space Complexity: O(n), where
n
is the number of elements in the stack, since both queues store elements.
Conclusion
In this coding challenge, we implemented a stack using two queues. We used two queues to simulate the Last In, First Out (LIFO) behavior of a stack. This approach demonstrates how we can manipulate queues to behave like a stack, providing efficient push()
operations while handling the complexity during pop()
and peek()
operations.
Comments
Post a Comment
Leave Comment