Master Queue & Deque Problems in Python

Image
  Queues look simple on paper, but they quietly decide how real systems behave under pressure. ⏱️ Estimated reading time: 12 minutes Generally accepted, queuing (queue) is a first-in-first-out ( FIFO ) data structure. In reality, queues are used in many non-academic contexts as a means of survival. All systems that deal with any kind of traffic, task, request, or data at scale eventually face this same fundamental problem: it is impossible for everything to be processed simultaneously. When traffic arrives at a system faster than it can be processed or handled, that system needs to determine what stays in the queue, what is dropped, and the order in which it will process traffic. At this point, we begin to view queues as more than just a structure for storing data; they also represent the design of a system. In large-scale systems (i.e., an e-commerce site selling out of an item due to demand and the associated product returns and replacement orders; an online video platf...

Mastering Stack Data Structures: Easy to Medium LeetCode Problems Explained

 Why is stack control critical in modern operating systems and software applications?

Stack as a control mechanism rather than just for data storage

The fundamental purpose of a stack is to provide an order of execution and control over the sequence of instructions executed by a computer. Stacks provide a level of predictability and safety in many real-life circumstances because certain procedures must be executed using a last-in, first-out (LIFO) approach. LIFO means that the most recently added stack entry must be the first to be implemented.

The following are a few ways that modern software takes advantage of stacks:

1.      Functional calls and recursive functions, with the last functional call always returning before any of the previous calls.

2.      The use of undo and redo functionalities, available in many software applications such as text editors and design applications.

3.      Expression evaluation is performed by the compiler and interpreter.

4.      Validation tasks such as matching brackets, nesting, and so on.

Stacks enable systems to stop, start and roll back the operation of the stack and thereby provide a mechanism to perform the task with the least amount of resources being used. Because stacks are so prevalent in compilers, operating systems, web browsers, and many services on the backend, we can now think of many of the challenges that we deal with daily as stemming from the need to control executions rather than only the storage of data.


🟢 Easy Stack Problems (Foundation Builders)

1.      Valid Parentheses







🧠 Explanation

An error commonly made by developers when validating brackets with regular expressions is only counting the number of opening and closing brackets or checking for matching brackets without considering the order. Brackets can be nested; therefore, it is important to know which one was opened last to determine if the brackets are balanced correctly. The string [)] has both a matched set of opening and closing brackets; however, due to incorrect ordering, the parentheses within this string are not considered to be properly nested/ordered.

⚙️Logic in Steps

1.      A reference structure determines which closing bracket corresponds with each opening bracket, enabling fast and accurate evaluations.

2.      An internal stack keeps track of any opening brackets that still need to be matched to closing brackets as the string is processed.

3.      Each character in the string is checked individually to ensure that opening and closing brackets are in the correct order per their defined rules.

4.      Once an opening bracket has been added to the stack, it cannot be removed without first locating its matching closing bracket.

5.      Whenever a closing bracket is encountered, the last opening bracket added to the stack will be checked against that closing bracket for validity.

6.      If a closing bracket appears without a prior opening bracket being found, a safety check will be made to identify this error.

7.      As long as no mismatches are located along the way, at the end of the string, the open brackets remaining in the stack must all have been matched by closing brackets.

Time and Space Complexity

1.       Time Complexity: O(n)
Each character in the string is processed exactly once.

2.       Space Complexity: O(n)
In the worst case, all characters are opening brackets and are stored in the stack.

📝 Key Takeaways from This Problem and Solution

  1. Parentheses validation requires maintaining the order of operations, not just balance.
  2. Stack is the most efficient structure for handling nested and dependent elements.
  3. Immediate failure on mismatch improves efficiency and avoids unnecessary processing.
  4. Safe handling of empty stack conditions prevents runtime errors.
  5. A final validation step ensures complete correctness

2. Baseball Game

 




🧠 Explanation

One mistake that's easy to make when calculating your "score" in real time is that people often try to "just" make calculations that depend on the history of all your prior scores. Often it doesn't work correctly because of certain operations that depend on how your total score directly influences the calculations of “C” (cancel), “D” (double), and “+” (adding the last two scores together). These operations cannot be effectively completed without maintaining a continuous score history.

⚙️Logic in Steps

1.     The Stack class keeps track of valid scores in the order they happen.

2.     Every operation will be executed in the order it was called; therefore, this is a sequential process.

3.     When you enter a score, it will be added as a number directly onto the stack.

4.     When you enter a double operation, it will take the last element off the top of the stack (i.e., score) and put a double (i.e., 2 times the score) on the stack.

5.     The cancel operation cancels the last score entered by popping the last element off the stack.

6.     When you perform a sum operation, it will take the last two numbers off the top of the stack and add them together, then push that new score on to the top of the stack.

7.     A helper method will allow you to access the top of the stack safely, preventing runtime exceptions when the stack is empty.

8.     Once all operations are completed, you can get the total score by adding all scores (i.e., elements of the stack) together.

This method works well if you want to use a stack method to keep track of history for undo/rollback features.

Time and Space Complexity

1.      Time Complexity: O(n)
Each operation is processed exactly once.

  1. Space Complexity: O(n)
    The stack stores all valid scores generated during execution

  📝Key Takeaways from This Problem and Solution

1.     Stacks are ideally suited for solving undo, rollback, or dependency problems.

2.     Stacks are ideal because they maintain history, so previous results can be preserved.

3.     With a stack, it is easy to access recently added items in O(1) or constant time.

4.     Stack-based defensive checks improve the fault tolerance of the system by preventing runtime errors and failures.

5.     Good separation of logic improves maintainability and readability of code.


3.      Backspace String Compare



🧠 Explanation

When you process backspace inline by also comparing the strings directly, this is often an error because backspace only deals with the last character added. Without the previous state of the string, you will not be able to accurately know which characters were affected by this change. This can cause error in the actual comparisons of multiple backspaces, especially if done in a row.

⚙️Logic in Steps

1.     When dealing with each input string, backspace effects are simulated independently from one another and each backspace affects only the string from which it was applied to create a new string. The end result is a string where, after processing all backspaces for that string, we can see what characters are present as a result of our use of backspaces.

2.     The stack structure is what allows us to keep track of what valid characters we ended up with, after applying backspace operations.

3.     To add a regular character, you simply push the character onto the stack.

4.     To remove a regular character when a backspace character is seen, you will remove the last character added to the stack, assuming there was one.

5.     If there was an additional backspace operation applied to an empty stack at the very beginning, it has no effect.

6.     After processing both strings through the stack structure, you will end up with two processed strings that can be compared for equality.

Using the stack structure, we can accurately simulate how backspace operations work in modern text editors.

Time and Space Complexity

1.      Time Complexity: O(n + m)
Each character in both strings is processed exactly once.

2.      Space Complexity: O(n + m)
Auxiliary stacks are used to store the processed characters for both strings.

📝Key Takeaways from This Problem and Solution

1.     Due to their LIFO nature, stacks implement backspace operations very naturally.

2.     The necessity of maintaining history stems from the possibility for recent actions to undo prior actions.

3.     By separating the processing logic from the other components of the application, we increase clarity and support reuse.

4.     We use defensive checks to prevent invalid removal requests and runtime errors from occurring during execution.

5.     The stack simulation will always provide valid results for complex input patterns.


4. Implement Stack Using Queues



🧠 Explanation

A common mistake made by some developers is to use the queue's basic operations, i.e. enqueue and dequeue, but not to alter their order at all. Because of this, the queue behaves like a First In First Out (FIFO) queue, whereas stacks follow a Last In First Out (LIFO) rule. Therefore, removing elements from the front of the queue will always give you the oldest processed item rather than the most recently added item. This leads to Stack implementation errors.

⚙️Logic in Steps

1.      A single queue is used to simulate stack behavior while respecting queue constraints.

2.      Each new element is first added to the queue using a normal enqueue operation.

3.      After insertion, the existing elements in the queue are rotated to the back.

4.      This rotation ensures that the newly added element moves to the front of the queue.

5.      By maintaining this order, the front of the queue always represents the top of the stack.

6.      Removing an element from the queue directly corresponds to popping from the stack.

7.      Accessing the front element allows retrieval of the stack’s top without removal.

8.      An empty check is performed by verifying whether the queue contains any elements.

This approach follows a push-heavy stack simulation technique, where order correction is handled during insertion.

Time and Space Complexity

1.      Time Complexity: O(n)

a.      Push operation: O(n) due to rotation

b.      Pop operation: O(1)

c.      Top operation: O(1)

d.      Empty operation: O(1)

2.      Space Complexity: O(n)
The queue stores all stack elements.

📝Key Takeaways from This Problem and Solution

1.     Queues can behave like stacks by managing the order in which elements are inserted.

2.     To have LIFO behaviour, the order of the elements needs to be modified during the insertion process.

3.     The complexity of the "push" operation can be shifted to allow the "pop" and "top" operations to remain simple.

4.     An understanding of how to perform pop and top operations is more important than how data structures are defined through their storage formats.

5.     This concept illustrates that how data structures function determines their classification, rather than their storage method.


🟡 Medium Stack Problems (Pattern Expanders)

5. Next Greater Element I

 


 


🧠 Explanation

The brute-force approach is an example of a common mistake. In this approach, you search the entire set for every element to find the closest bigger number. Because of this, the time complexity will take O(n²) and won't work very well with large amounts of data or at all! The second common issue with this method is that people do not take into consideration how the elements are going to be compared with each other as a result of the circular aspect of the array which leads to partial comparisons and incorrect comparisons.

⚙️Logic in Steps

1.      The default value for all elements of the result array is -1, indicating that the item has not yet been solved.

2.      A stack is used to keep track of the indexes of the elements that do not have an associated next greater value.

3.      To achieve this circular behavior, the input array is traversed twice without any changes to its original structure.

4.      In each traversal, the current element of the array is checked against the element in the index at the top of the stack.

5.      If the current element is greater than the element at the index on the top of the stack, then the current element becomes the next greater element for that index.

6.      Once an index has been found to have its next greater value, it will be removed from the stack.

7.      This process continues until either the stack is empty or the current element is less than or equal to the current value in the index at the top of the stack.

8.      To prevent duplicate computations, the only time a new index will be pushed onto the stack is during the first traversal.

9.      Any index remaining in the stack after all traversals have concluded will remain as -1 to indicate that no elements were found that were greater than that index.

This method guarantees optimal performance because each element is processed with the use of a monotonically decreasing stack.

Time and Space Complexity

1.       Time Complexity: O(n)
Each element is pushed onto and popped from the stack at most once, resulting in linear time complexity despite the nested loop structure.

2.       Space Complexity: O(n)
Additional space is used for the stack and the result array.

📝Key Takeaways from This Problem and Solution

1.      The brute force approach is straightforward, but it becomes inefficient as the size of the input increases.

2.      Using a monotonic stack allows us to perform the same comparisons without worrying about duplicates.

3.      Circular arrays are easily managed through modular indexing.

4.      By storing indices instead of values, we can assign results directly without intermediate variables.

5.      Amortization can change how we interpret nested loops, which may not always be O(n²).


6.      Daily Temperatures




🧠 Explanation

One error many people make is implementing a brute force solution where you search future days for a warmer day for each day, leading to an O(n²) time complexity which does not scale well when the input size is large. Another error people commonly make is making their solution overly complex by using auxiliary data structures that are not needed instead of utilizing the properties of the stack data structure.

⚙️Logic in Steps

1.      Initialize an answer array to hold the number of days you’ll have to wait for a warmer temperature.

2.      To track days where you cannot find a warmer day yet, use a stack to maintain the index of those days.

3.      The stack is maintained in a monotonic decreasing temperature order.

4.      While processing through the array (left to right), assume that each day can potentially be a warmer day for all previous days than its index.

5.      If the temperature of today is greater than yesterday (at the top of the stack), then we have found out how many days we were waiting for a warmer day for the previous day.

6.      The difference in index values is the number of days waited for the previous day to have a warmer temperature.

7.      After you figure out how many days to wait for previous days, you remove the indexes from the stack.

8.      After today has been processed, you add the index value of today back onto the stack.

9.      Any remaining days in the stack at this point should all have a value of zero because a warmer day does not exist.

10.  By using the monotonic stack pattern, you can efficiently find the answer to many waiting days at once.

Time and Space Complexity

1.       Time Complexity: O(n)
Each index is pushed to and popped from the stack at most once, ensuring linear time complexity.

2.       Space Complexity: O(n)
Additional space is used for the stack and the result array.

📝Key Takeaways from This Problem and Solution

1.      Brute-force approaches are obvious solutions but are very low in efficiency.

2.      Monotonic Stack removes the need to compare elements repeatedly.

3.      Storing the results using an index allows for simplified result algorithms.

4.      This method will be frequently employed to resolve range and comparison issues.

5.      Recognizing how the stack operates can help improve the efficiency of array-related issues.


7. Evaluate Reverse Polish Notation



🧠 Explanation

Evaluating the expressions based on infix rules or operator precedence is a common mistake. Instead of a simpler evaluation process, this creates complexity in the logic and may produce incorrect results. A common mistake is to use floor division to divide. Dividing floor-style will give incorrect answers when negative numbers are used. Therefore, Reverse Polish Notation must always be evaluated using a stack, and does not use operator precedence.

⚙️Logic in Steps

1.      A stack is used for storing operands while performing an operation with an expression. When evaluating an expression, each token will be processed left to right.

2.      NUMBERS are treated as OPERANDS (pushed onto the stack), and when we encounter an OPERATOR (e.g., +, -, *, / etc.), we pop the TWO operands off the STACK and then perform the OPERATION (i.e. left to right).

3.      After performing the OPERATION, we push the RESULT back onto the STACK.

4.      As DIVISIONS should be calculated towards zero, thus careful consideration must be taken when handling DIVISION when using this method.

5.      When all tokens have been processed, you will end up with ONLY one value remaining on the Stack which will be your FINAL Result of your Evaluation of the EXPRESSION.

6.      This method represents how POSTFIX EXPRESSION as used by CALCULATOR and COMPILER are evaluated.

Time and Space Complexity

1.       Time Complexity: O(n)
Each token is processed exactly once.

2.       Space Complexity: O(n)
The stack stores operands during evaluation.

📝Key Takeaways from This Problem and Solution

1.      Reverse Polish Notation is a natural fit for a stack-based approach to the evaluation of expressions.

2.      In a postfix expression, operator precedence does not exist.

3.      The order of operands must be maintained for the correct operation of subtraction and division.

4.      A failure to handle division correctly will introduce the possibility of subtle logical errors into the application.

5.      Code using the stack-based evaluation approach is cleaner and more maintainable.


8. Mini Stack

 



🧠 Explanation

One of the more frequently made errors while determining the minimum value of a Stack when using the getMin() method is that the developer will iterate through the entire Stack each time this method is called to find the lowest value within the Stack Collection. This practice results in O(n) Time Complexity for getMin() and violates the requirement of having a constant-time operation for this method. Another error developers often make is they remove or clear the Stack to find the minimum. By doing this, the data will no longer exist resulting in incorrect Stack functionality.

⚙️Logic in Steps

1.      Two stacks are used to separate the different functions of pushing and returning items from the stack.

2.      The first stack is the main stack for all values that the user has pushed onto it.

3.      The second stack is a supporting stack that stores the current minimum value at each point in time.

4.      When the user pushes a new item onto the primary stack, the supporting stack will be updated only under certain conditions (if the new value is less than or equal to the existing minimum value).

5.      If the user performs a pop operation and the item removed equals the minimum value, then it will also be removed from the supporting stack.

6.      The top operation will return the value of the item that was pushed onto the main stack most recently.

7.      The minimum operation will return the value on top of the supporting stack and will always be done in constant time.

8.      This design allows the user to have immediate access to the minimum value without performing an entire scan of the main stack.

Time and Space Complexity

1.       Time Complexity: O(1)

a.       Push: O(1)

b.       Pop: O(1)

c.       Top: O(1)

d.       GetMin: O(1)

2.       Space Complexity: O(n)
Additional space is used for the auxiliary minimum stack.

📝Key Takeaways from This Problem and Solution

1.      Auxiliary data structure for constant-time constraints.

2.      Creating separation of responsibilities improves clarity and performance.

3.      Record keeping of historical minimums prevents repeated calculations.

4.      Incrementation of stack problems might require creative use of space and time in order to solve them.

5.      This pattern is common in real-life systems and in interviews.


 Conclusion

 This blog discussed how stack-based concepts grow from straightforward validation challenges into more sophisticated optimization methods. Through eight distinct examples, we understood how stacks can effectively record and sequence the history, arrangement, or direct correlation of dependent objects within different contexts. Most notably, through recognising patterns such as ‘monotonic stack’ and ‘auxiliary stack’, we were able to derive optimal solutions to initially thought ‘brute-force’ techniques. Also, acquiring these patterns helps build a robust platform for solving complex interview-level questions and the same level of problem-solving ability in the workplace.


If you missed the Previous part

If you enjoyed reading this stack-oriented tutorial, please take a moment to look at the foundation upon which it is built on. In the last blog, I discussed array patterns and solutions with practical experience, and real-world LeetCode examples. To review that blog, please follow this link:

Array Problem-Solving Patterns Explained Using LeetCode Examples

 Together, these two blogs form a solid base for tackling core data structure problems with confidence.


Coming Up

In our upcoming blog we will examine how queues can be utilized to solve problems through problem-solving patterns based upon queues. This will include examples of the use of FIFO or, First-In-First-Out, to schedule, buffer and manage processes or events occurring in real-time. Additionally, we will examine a series of examples of queue or deque based problems that arise both during technical interviews and actual production cases. Once we've completed this discussion we will have a solid foundation for approaching the more advanced patterns found in working with data structures listed above.

Master Queue & Deque Problems in Python


Closing Whispers

Some problems wait, stacked quietly in time,
Answers rise when order starts to rhyme.
What we push today may return tomorrow’s key,
In layers of logic, clarity learns to be.

 

If you liked this, Follow Raw Unfiltered Talks

Comments

Popular posts from this blog

Jee Honest Journey

The hidden price of starting late for the exams like JEE

Brace Yourself: The Coming Wave Is Closer Than You Think