Stack Pattern
Stack Pattern
A Stack is a fundamental linear data structure that operates on the Last-In, First-Out (LIFO) principle. The last element added to the stack (via "push") is the first one to be removed (via "pop"). This makes stacks highly effective for problems where the most recently visited elements are the most relevant for subsequent decisions—such as managing function calls, backtracking, processing nested structures, or searching for nearby boundaries (monotonic stacks).
By utilizing a stack, we can keep track of historical states and resolve them in reverse order of arrival in O(N) time.
The Core Concept: Valid Parentheses
To understand this concept deeply, let's explore Valid Parentheses.
Problem Statement:
Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. An input string is valid if open brackets are closed by the same type of brackets and in the correct order.
Naive Brute Force — O(N2)
A naive solution repeatedly searches for matching pairs (like "()", "{}", "[]") and replaces them with an empty string until no more matching pairs are found. If the final string is empty, it is valid:
def isValidBrute(s: str) -> bool:
while "()" in s or "{}" in s or "[]" in s:
s = s.replace("()", "").replace("{}", "").replace("[]", "")
return len(s) == 0This is highly inefficient because searching and replacing strings takes O(N) time, and we may have to repeat this up to O(N) times, resulting in a worst-case time complexity of O(N2).
The Stack Breakthrough — O(N)
We can solve this in O(N) time and O(N) space using a stack.
As we iterate through the string:
- If we see an opening bracket (
'(','{','['), we push it onto the stack, saving it to match later. - If we see a closing bracket (
')','}',']'), it must match the most recently opened bracket, which is sitting at the top of the stack.- If the stack is empty (meaning there's no matching opener) or the top of the stack does not match the closing bracket, the string is invalid.
- If it matches, we pop the opener from the stack and continue.
- After processing the entire string, the stack must be empty (all opened brackets were matched and closed).
This allows us to validate nested structures in a single linear pass.
Step-by-Step Traversal
Let's trace the algorithm on the input string: "({[]})".
Step 1: Initialize Stack
We start by initializing our empty stack.
Step 2: Push Opening Brackets
- We process character
'('. Since it is an opening bracket, we push it onto the stack.
- Next, we see
'{'. It is also an opening bracket, so we push it onto the stack.
- Then, we see
'['. We push it onto the stack. The stack is now['(', '{', '['].
Step 3: Match and Pop Closing Brackets
- We see character
']'. Since it is a closing bracket, we check the top of the stack. - The top of the stack is
'[', which matches']'.
- We pop
'['from the stack. The stack becomes['(', '{'].
- We see character
'}'. The top of the stack is'{', which matches'}'.
- We pop
'{'from the stack. The stack becomes['('].
- We see character
')'. The top of the stack is'(', which matches')'.
- We pop
'('from the stack. The stack is now empty.
Step 4: Done
The string has been fully processed. We verify if the stack is empty.
Since the stack is empty, the string has valid parentheses. We return True.
Code Implementation
def isValid(s: str) -> bool:
stack = []
# Map closing brackets to their corresponding opening brackets
mapping = {")": "(", "}": "{", "]": "["}
for char in s:
# If the character is a closing bracket
if char in mapping:
# Pop the top element from stack if it is not empty, otherwise use dummy
top_element = stack.pop() if stack else '#'
# If the mapping does not match the popped opening bracket
if mapping[char] != top_element:
return False
else:
# It is an opening bracket, push to stack
stack.append(char)
# Return true if stack is empty (all brackets matched)
return len(stack) == 0Time Complexity: O(N) — we scan the string once. Each character is pushed and popped at most once.
Space Complexity: O(N) — in the worst case, the stack stores all opening brackets (e.g., "(((((").
Core Variations
1. Nested Parsing / Decoding
Using stacks to decode expressions or strings that are recursively nested.
- Approach: Push multipliers and characters onto stacks, and when a closing marker is hit, pop and reconstruct the inner segment.
- Examples: Decode String, Basic Calculator.
2. Monotonic Stack
A stack that maintains its elements in strictly increasing or decreasing order.
- Approach: When pushing a new element, pop all elements that violate the sorting property. This allows finding the "next greater" or "previous smaller" element in O(N) overall time.
- Examples: Daily Temperatures, Next Greater Element, Largest Rectangle in Histogram.
3. Min Stack / Max Stack
A stack that supports retrieving the minimum or maximum element in O(1) time.
- Approach: Maintain an auxiliary stack that stores the minimum/maximum element seen so far at each level of the main stack.
- Example: Min Stack.
When Do I Use This?
Ask yourself these questions:
- Does the problem involve processing data where the most recently seen item dictates the next step?
- Are you parsing nested structures like parentheses, nested strings, HTML tags, or equations?
- Do you need to find the next greater or previous smaller element for each item in a list?
If any of these apply, a Stack is the ideal data structure.
Practice Problems
| Done | Question | Difficulty |
|---|---|---|
| Valid Parentheses | Easy | |
| Decode String | Medium | |
| Longest Valid Parentheses | Hard | |
| Daily Temperatures | Medium | |
| Largest Rectangle in Histogram | Hard |
How helpful was this content?
Comments
Sign in to join the discussion