Problem: Implement Queue using StacksLeetCode Problem Number: 232
- Difficulty Level: Easy
- The problem asks to design a queue using two stacks. A queue should support the push, pop, peek, and empty operations.
- Queue is a First-In-First-Out (FIFO) data structure, and Stack is a Last-In-First-Out (LIFO) data structure. So, we need to figure out a way to reverse the order of elements.
# Inputs
# push(1)
# push(2)
# peek()? # returns 1
# pop()? ?# returns 1
# empty() # returns false
:
Questions for Interviewer:
- What should the function return if we attempt to pop or peek when the queue is empty?
- Is it safe to assume that the functions push, pop and peek will be called validly (i.e., pop and peek will not be called on an empty queue)?
- What are the expected time complexity constraints for each operation?
- The queue is empty and we are trying to pop or peek.
- Only one item in the queue, in which case pop will make it empty.
- Using two stacks, we can ensure that the oldest element can be retrieved in O(1) time complexity. Stack 1 is used for enqueue operation and Stack 2 for dequeue operation.
- We use two stacks, input and output.
- For the push operation, we simply push the element onto the input stack.
- For the pop operation, we peek/pop from the output stack. If the output stack is empty, we first move all elements from the input stack to the output stack.
- For the peek operation, it is the same as pop but we return the topmost element without removing it.
- For empty, return true if both the stacks are empty.
class MyQueue:
? ? def __init__(self):
? ? ? ? """
? ? ? ? Initialize your data structure here.
? ? ? ? """
? ? ? ? self.input = []
? ? ? ? self.output = []
? ? def push(self, x: int) -> None:
? ? ? ? """
? ? ? ? Push element x to the back of queue.
? ? ? ? """
? ? ? ? self.input.append(x)
? ? def pop(self) -> int:
? ? ? ? """
? ? ? ? Removes the element from in front of queue and returns that element.
? ? ? ? """
? ? ? ? self.peek()
? ? ? ? return self.output.pop()
? ? def peek(self) -> int:
? ? ? ? """
? ? ? ? Get the front element.
? ? ? ? """
? ? ? ? if not self.output:
? ? ? ? ? ? while self.input:
? ? ? ? ? ? ? ? self.output.append(self.input.pop())
? ? ? ? return self.output[-1]
? ? def empty(self) -> bool:
? ? ? ? """
? ? ? ? Returns whether the queue is empty.
? ? ? ? """
? ? ? ? return self.input == [] and self.output == []
Explanation to a Non-Technical Person:
- Imagine you have two stacks of plates (let's call them stack A and stack B).
- When you add a plate, you place it on top of stack A.
- When you want to remove a plate, you would normally take it from stack A. But that would mean the last plate you put in comes out first (like a stack of plates). We want the first plate we put in to come out first (like a queue).
- So, we take all plates from stack A and put them on stack B (one by one, taking from the top). Now the bottom plate from stack A ends up on top of stack B.
- We then remove the top plate from stack B (which was the first plate we added originally). So, we've just simulated a queue using two stacks of plates!
- We use two lists (input and output) as two stacks.
- In the push function, we simply append the new element to the input stack.
- In the pop function, if output is empty, we transfer all elements from input to output (thereby reversing their order), and then pop the top element of output.
- The peek function is like pop, but we don't remove the top element of output.
- The empty function checks if both stacks are empty.
- This problem demonstrates the pattern of using one data structure to simulate another. Here, we're using stacks (LIFO) to implement a queue (FIFO).
- The trick is to use two stacks so we can reverse the order of elements. The process of transferring elements from one stack to the other essentially flips their order.
- This is a good example of a design problem where understanding the properties of data structures and how they can be combined is essential.
- For the push operation, the time complexity is O(1) because we are simply appending an element to a list in Python which is a constant time operation.
- For the pop and peek operations, in the worst case scenario (when the output stack is empty), we need to transfer all the elements from the input stack to the output stack. This gives a time complexity of O(n), where n is the number of elements in the queue. However, each element only ever gets transferred once from input to output, and then gets popped once. So the average time complexity for these operations is O(1).
- For the empty operation, the time complexity is O(1) because we are just checking whether two lists are empty.
Points to remember for the future:
- This problem is a classic data structures problem that demonstrates how one data structure (in this case, a stack) can be used to implement another (a queue). It's important to understand the properties of different data structures and how they can be combined in different ways to solve problems.
- The trick here is to use two stacks so that the order of elements can be reversed, effectively turning the LIFO (last-in-first-out) behavior of a stack into the FIFO (first-in-first-out) behavior of a queue.
Line-by-Line Code Explanation:
- def __init__(self): This line is initializing our class. We are creating two stacks, input and output.
- def push(self, x: int) -> None: This function is used to insert an element into our queue. We do this by simply pushing the element onto our input stack.
- def pop(self) -> int: This function is used to remove an element from our queue. To do this, we first check if the output stack is empty. If it is, we pop all elements from the input stack and push them onto the output stack. Then we return the top element of the output stack.
- def peek(self) -> int: This function is used to get the front element of the queue. It does the same as the pop function, but without actually removing the element from the output stack.
- def empty(self) -> bool: This function checks if our queue is empty by checking if both our stacks are empty.
- Class definition: class MyQueue:
- Function definition inside a class: def function_name(self, parameters):
- Initialization of class variables: self.input = [], self.output = []
- List operations: append() for pushing an element onto a stack, pop() for removing the top element from a stack, and [-1] for accessing the top element without popping it.
- Checking if a list is empty: if not self.input:, if not self.output:.
Week 1 Ended - Total Problems 13 - All are Easy