Solve 10 classic stack interview problems with full solutions — valid parentheses, evaluate reverse polish notation, daily temperatures, next greater element, asteroid collision, largest rectangle in histogram, remove k digits, decode string, and more.
Introduction
Stack problems appear in every major technical interview. The fundamental insight that makes stacks powerful for interviews: they naturally handle "the most recent item that satisfies a condition" type of problems. Whenever you need to track context that grows and shrinks in LIFO order, a stack is likely the right tool.
Problem 2 — Evaluate Reverse Polish Notation
Problem: Evaluate the value of an arithmetic expression in Reverse Polish Notation (postfix).
["2","1","+","3","*"] → ((2+1)*3) = 9
["4","13","5","/","+"] → (4+(13/5)) = 6
def eval_rpn(tokens):
"""
Push numbers. When operator found, pop two operands and apply.
Time: O(n), Space: O(n)
"""
stack = []
operators = {
'+': lambda a, b: a + b,
'-': lambda a, b: a - b,
'*': lambda a, b: a * b,
'/': lambda a, b: int(a / b), # Truncate toward zero
}
for token in tokens:
if token in operators:
b = stack.pop() # Second operand (popped first)
a = stack.pop() # First operand
stack.append(operators[token](a, b))
else:
stack.append(int(token))
return stack[0]
print(eval_rpn(["2","1","+","3","*"])) # 9
print(eval_rpn(["4","13","5","/","+"])) # 6
print(eval_rpn(["10","6","9","3","+","-11","*","/","*","17","+","5","+"])) # 22
Problem 3 — Daily Temperatures
Problem: Given array temperatures, return an array answer where answer[i] is the number of days you have to wait after day i to get a warmer temperature. If no such day exists, set answer[i] = 0.
[73,74,75,71,69,72,76,73] → [1,1,4,2,1,1,0,0]
def daily_temperatures(temperatures):
"""
Monotonic decreasing stack: stores indices of unresolved days.
When a warmer day is found, pop and compute the gap.
Time: O(n), Space: O(n)
"""
n = len(temperatures)
result = [0] * n
stack = [] # Indices of days waiting for warmer temperature
for i, temp in enumerate(temperatures):
# Pop all days that are colder than current day
while stack and temperatures[stack[-1]] < temp:
prev_day = stack.pop()
result[prev_day] = i - prev_day # Days waited
stack.append(i) # Current day is still unresolved
return result
print(daily_temperatures([73,74,75,71,69,72,76,73]))
# [1, 1, 4, 2, 1, 1, 0, 0]
Trace:
| i=0, T=73 | stack=[0] |
| i=1, T=74: 73<74 | pop 0, result[0]=1. stack=[1] |
| i=2, T=75: 74<75 | pop 1, result[1]=1. stack=[2] |
| i=3, T=71 | stack=[2,3] |
| i=4, T=69 | stack=[2,3,4] |
| i=5, T=72: 69<72 | pop 4, result[4]=1. 71<72 → pop 3, result[3]=2. stack=[2,5] |
| i=6, T=76: 72<76 | pop 5, result[5]=1. 75<76 → pop 2, result[2]=4. stack=[6] |
| i=7, T=73 | stack=[6,7] |
Problem 4 — Next Greater Element
Problem: For each element in array, find the next element to the right that is greater. Return -1 if none exists.
[2,1,2,4,3] → [4,2,4,-1,-1]
def next_greater_element(nums):
"""
Monotonic stack (decreasing from bottom to top).
When we find an element greater than top, pop and assign.
Time: O(n), Space: O(n)
"""
result = [-1] * len(nums)
stack = [] # Indices
for i in range(len(nums)):
while stack and nums[stack[-1]] < nums[i]:
idx = stack.pop()
result[idx] = nums[i]
stack.append(i)
return result
# For circular array (NGE I variation):
def next_greater_circular(nums):
"""Next greater element in a circular array."""
n = len(nums)
result = [-1] * n
stack = []
for i in range(2 * n): # Iterate twice to simulate circular
actual_i = i % n
while stack and nums[stack[-1]] < nums[actual_i]:
idx = stack.pop()
result[idx] = nums[actual_i]
if i < n:
stack.append(actual_i)
return result
print(next_greater_element([2,1,2,4,3])) # [4,2,4,-1,-1]
print(next_greater_circular([1,2,1])) # [2,-1,2]
Problem 5 — Asteroid Collision
Problem: Array of asteroids moving right (positive) or left (negative). Find the state after all collisions. Collision rule: smaller magnitude asteroid is destroyed; equal magnitudes both destroyed.
| [5,10,-5] | [5,10] (5 and 10 go right, -5 goes left, hits 10 but 10>5) |
| [8,-8] | [] (8 right, -8 left, equal — both destroyed) |
| [10,2,-5] | [10] (2 hit by -5, 2<5 dies; -5 hits 10, 10>5, -5 dies) |
def asteroid_collision(asteroids):
"""
Stack holds asteroids that haven't collided yet.
Right-moving asteroids are pushed.
Left-moving asteroids may destroy top of stack (right-moving).
Time: O(n), Space: O(n)
"""
stack = []
for asteroid in asteroids:
alive = True # Current asteroid is still alive
# Collision: current is left-moving (-), stack top is right-moving (+)
while alive and asteroid < 0 and stack and stack[-1] > 0:
if stack[-1] < -asteroid:
stack.pop() # Stack asteroid smaller — it dies
elif stack[-1] == -asteroid:
stack.pop() # Equal — both die
alive = False
else:
alive = False # Current asteroid smaller — it dies
if alive:
stack.append(asteroid)
return stack
print(asteroid_collision([5,10,-5])) # [5,10]
print(asteroid_collision([8,-8])) # []
print(asteroid_collision([10,2,-5])) # [10]
print(asteroid_collision([-2,-1,1,2])) # [-2,-1,1,2] (no collisions)
Problem 6 — Largest Rectangle in Histogram
Problem: Given an array of bar heights, find the largest rectangle that can be formed.
heights = [2,1,5,6,2,3]
Largest rectangle area = 10 (using bars at index 2,3 with height 5)
def largest_rectangle(heights):
"""
Monotonic increasing stack.
For each bar, pop taller bars and compute the rectangle they could form.
Time: O(n), Space: O(n)
"""
stack = [] # Indices of bars in increasing height order
max_area = 0
n = len(heights)
for i in range(n + 1):
current_height = heights[i] if i < n else 0 # Sentinel 0 at end
while stack and heights[stack[-1]] > current_height:
height = heights[stack.pop()]
# Width: from stack[-1]+1 (or 0) to i-1
width = i if not stack else i - stack[-1] - 1
max_area = max(max_area, height * width)
stack.append(i)
return max_area
print(largest_rectangle([2,1,5,6,2,3])) # 10
print(largest_rectangle([2,4])) # 4
Why this works: When we pop a bar of height h at index k, the rectangle with height h extends:
- Left boundary: just after the new stack top (or from 0 if stack is empty)
- Right boundary: just before the current index i
- Width = i - stack[-1] - 1
Problem 7 — Remove K Digits
Problem: Remove k digits to make the remaining number as small as possible.
num="1432219", k=3 → "1219"
num="10200", k=1 → "200"
def remove_k_digits(num, k):
"""
Monotonic stack (increasing from bottom).
Remove larger digits that appear before smaller ones.
Time: O(n), Space: O(n)
"""
stack = []
for digit in num:
# Pop digits larger than current if k allows
while k > 0 and stack and stack[-1] > digit:
stack.pop()
k -= 1
stack.append(digit)
# If k > 0, remove from the end (already nearly sorted)
if k > 0:
stack = stack[:-k]
# Remove leading zeros and convert to string
result = ''.join(stack).lstrip('0')
return result if result else '0'
print(remove_k_digits("1432219", 3)) # "1219"
print(remove_k_digits("10200", 1)) # "200"
print(remove_k_digits("10", 2)) # "0"
Problem 8 — Decode String
Problem: Decode string where k[encoded_string] means repeat encoded_string k times.
"3[a]2[bc]" → "aaabcbc"
"3[a2[c]]" → "accaccacc"
def decode_string(s):
"""
Use two stacks: one for counts, one for strings built so far.
Time: O(output length), Space: O(output length)
"""
count_stack = [] # Pending repetition counts
string_stack = [] # Partially built strings
current_string = ""
current_count = 0
for char in s:
if char.isdigit():
current_count = current_count * 10 + int(char) # Multi-digit numbers
elif char == '[':
count_stack.append(current_count)
string_stack.append(current_string)
current_count = 0
current_string = ""
elif char == ']':
count = count_stack.pop()
prev_string = string_stack.pop()
current_string = prev_string + current_string * count
else:
current_string += char
return current_string
print(decode_string("3[a]2[bc]")) # "aaabcbc"
print(decode_string("3[a2[c]]")) # "accaccacc"
print(decode_string("2[abc]3[cd]ef")) # "abcabccdcdcdef"
Problem 9 — Trapping Rain Water (Stack Approach)
Problem: (Previously solved with two pointers — here is the stack-based approach.)
def trap_water_stack(heights):
"""
Horizontal layers approach using a stack.
Stack stores indices of bars that might form left boundaries.
Time: O(n), Space: O(n)
"""
water = 0
stack = []
for i, height in enumerate(heights):
while stack and heights[stack[-1]] < height:
bottom = stack.pop()
if not stack:
break
left = stack[-1]
width = i - left - 1
bounded_height = min(heights[left], height) - heights[bottom]
water += width * bounded_height
stack.append(i)
return water
print(trap_water_stack([0,1,0,2,1,0,1,3,2,1,2,1])) # 6
Problem 10 — Maximum Frequency Stack
Problem: Implement a stack that pops the most frequently occurring element. On a tie, pop the most recently pushed among the most frequent.
from collections import defaultdict
class FreqStack:
"""
Track frequency of each element and the stack of elements at each frequency.
push: O(1), pop: O(1)
"""
def __init__(self):
self.freq = defaultdict(int) # element → frequency
self.group = defaultdict(list) # frequency → stack of elements
self.max_freq = 0
def push(self, val: int) -> None:
self.freq[val] += 1
f = self.freq[val]
self.max_freq = max(self.max_freq, f)
self.group[f].append(val)
def pop(self) -> int:
val = self.group[self.max_freq].pop()
self.freq[val] -= 1
if not self.group[self.max_freq]:
self.max_freq -= 1
return val
# Testing:
fs = FreqStack()
fs.push(5); fs.push(7); fs.push(5); fs.push(7); fs.push(4); fs.push(5)
print(fs.pop()) # 5 (most frequent, freq=3)
print(fs.pop()) # 7 (5 and 7 both freq=2, 7 pushed more recently)
print(fs.pop()) # 5
print(fs.pop()) # 4
The Monotonic Stack Pattern
Problems 3, 4, 6, 7 all use a monotonic stack — a stack that maintains a monotonically increasing or decreasing order of its elements.
Monotonic Decreasing: Elements in stack are in decreasing order (top = smallest). Used for "next greater element" problems.
Monotonic Increasing: Elements in stack are in increasing order (top = largest). Used for "next smaller element" and histogram problems.
Template:
def monotonic_stack_template(arr):
stack = [] # Indices
result = [-1] * len(arr) # Default answer
for i in range(len(arr)):
# For monotonic decreasing: pop while top is LESS than arr[i]
while stack and arr[stack[-1]] < arr[i]:
idx = stack.pop()
result[idx] = arr[i] # arr[i] is the answer for idx
stack.append(i)
return result
Summary
| Problem | Stack Type | Key Insight |
|---|
| Valid Parentheses | Simple | Push opens, match on close |
| Evaluate RPN | Simple | Push numbers, apply on operator |
| Daily Temperatures | Monotonic decreasing | Pop when warmer day found |
| Next Greater Element | Monotonic decreasing | Pop when greater element found |
| Asteroid Collision | Simulation | Pop right-movers when hit |
| Largest Rectangle | Monotonic increasing | Pop taller bars to compute area |
| Remove K Digits | Monotonic increasing | Remove larger digits before smaller |
| Decode String | Two stacks | Count stack + string stack |
| Trap Rain Water | Monotonic | Horizontal layer computation |
| Max Frequency Stack | Multi-stack | Stack per frequency level |
*Stacks section complete. Next: Queues — Introduction*