Introduction
Coding interview questions often reuse common stack and queue patterns. Below, we highlight some of the most frequently appearing variants from LeetCode, each illustrating a key concept or twist. We organize them into stack-based and queue-based problems, and for each variant we provide the unique tweak to the base template, an example problem, a step-by-step walkthrough, and solution code when the pattern deviates significantly from the basics.
Stack Variants
Balanced Parentheses (Stack for Matching Pairs)
Modified Template: Use a stack to track opening brackets. Push opening symbols; for every closing symbol, pop and check if it matches the expected type. The tweak is a conditional pop based on matching types. At the end, the stack must be empty.
Example Problem: LeetCode 20 – Valid Parentheses. Given s = "()[]{}", determine if valid.
- Read
{. Push{. Stack:[{]. - Read
[. Push[. Stack:[{, []. - Read
(. Push(. Stack:[{, [, (]. - Read
). Top is(, matches. Pop. Stack:[{, []. - Read
]. Top is[, matches. Pop. Stack:[{]. - Read
}. Top is{, matches. Pop. Stack:[](empty).
def isValid(s: str) -> bool:
bracket_map = {')':'(', '}':'{', ']':'['}
stack = []
for char in s:
if char in '([{':
stack.append(char)
else: # closing bracket
if not stack or stack[-1] != bracket_map[char]:
return False
stack.pop()
return not stack
Monotonic Stack (Next Greater Element / Daily Temperatures)
Modified Template: Maintain a monotonic (usually decreasing) stack of values or indices. Pop from stack when a new element breaks the monotonic property (e.g., new element is greater). This finds the "next greater" element for popped items.
Example Problem: LeetCode 739 – Daily Temperatures. Find days until a warmer temperature.
- Day 0 (73): Stack empty, push 0. Stack:
[0]. Answer:[0,0,0,0,0,0]. - Day 1 (74): 74 > 73. Pop 0.
answer[0]=1-0=1. Push 1. Stack:[1]. Answer:[1,0,0,0,0,0]. - Day 2 (71): 71 < 74. Push 2. Stack:
[1,2]. - Day 3 (69): 69 < 71. Push 3. Stack:
[1,2,3]. - Day 4 (72): 72 > 69. Pop 3.
answer[3]=4-3=1. 72 > 71. Pop 2.answer[2]=4-2=2. 72 < 74. Push 4. Stack:[1,4]. Answer:[1,0,2,1,0,0]. - Day 5 (76): 76 > 72. Pop 4.
answer[4]=5-4=1. 76 > 74. Pop 1.answer[1]=5-1=4. Push 5. Stack:[5]. Answer:[1,4,2,1,1,0].
answer[5]=0. Final: [1,4,2,1,1,0].
def dailyTemperatures(temperatures: list[int]) -> list[int]:
n = len(temperatures)
answer = [0] * n
stack = [] # stores indices
for i, temp in enumerate(temperatures):
while stack and temperatures[stack[-1]] < temp:
j = stack.pop()
answer[j] = i - j
stack.append(i)
return answer
Min Stack (Stack with Constant-Time Min Tracking)
Modified Template: Design a stack supporting push, pop, top, and getMin() in O(1) time. Maintain an auxiliary stack (or store min with each element) to track the current minimum.
Example Problem: LeetCode 155 – Min Stack.
init(): stack=[], min_stack=[]push(5): stack=[5], min_stack=[5] (min=5)push(2): stack=[5,2], min_stack=[5,2] (min=2)push(7): stack=[5,2,7], min_stack=[5,2,2] (min=2, as 7>2)getMin(): returns top of min_stack (2)pop(): pops 7 from stack, 2 from min_stack. stack=[5,2], min_stack=[5,2] (min=2)pop(): pops 2 from stack, 2 from min_stack. stack=[5], min_stack=[5] (min=5)
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, x: int):
self.stack.append(x)
if not self.min_stack: self.min_stack.append(x)
else: self.min_stack.append(min(x, self.min_stack[-1]))
def pop(self):
self.stack.pop()
self.min_stack.pop()
def top(self) -> int: return self.stack[-1]
def getMin(self) -> int: return self.min_stack[-1]
Largest Rectangle in Histogram
Modified Template: Use a monotonic increasing stack of bar indices. Pop when a lower bar is met, calculating area for the popped bar. Append a zero-height bar at end to flush stack.
Example Problem: LeetCode 84 – Largest Rectangle in Histogram.
- idx 0 (h=2): Stack empty, push 0. Stack:[0]. MaxArea=0.
- idx 1 (h=1): 1 < h[0]=2. Pop 0. width=1. area=2*1=2. MaxArea=2. Push 1. Stack:[1].
- idx 2 (h=5): 5 > h[1]=1. Push 2. Stack:[1,2].
- idx 3 (h=6): 6 > h[2]=5. Push 3. Stack:[1,2,3].
- idx 4 (h=2): 2 < h[3]=6. Pop 3. height=6, width=4-2-1=1. area=6. MaxArea=6.
Now 2 < h[2]=5. Pop 2. height=5, width=4-1-1=2. area=10. MaxArea=10.
2 > h[1]=1. Push 4. Stack:[1,4]. - idx 5 (h=3): 3 > h[4]=2. Push 5. Stack:[1,4,5].
- idx 6 (h=0, sentinel): 0 < h[5]=3. Pop 5. height=3, width=6-4-1=1. area=3. MaxArea=10.
0 < h[4]=2. Pop 4. height=2, width=6-1-1=4. area=8. MaxArea=10.
0 < h[1]=1. Pop 1. height=1, width=6-(-1)-1=6. area=6. MaxArea=10.
Push 6. Stack:[6].
def largestRectangleArea(heights: list[int]) -> int:
stack = [] # indices
max_area = 0
for i, h in enumerate(heights + [0]):
while stack and heights[stack[-1]] > h:
height = heights[stack.pop()]
left_idx = stack[-1] if stack else -1
width = i - left_idx - 1
max_area = max(max_area, height * width)
stack.append(i)
return max_area
Queue Variants
BFS on Matrix/Grid (Multi-source BFS – Rotting Oranges)
Modified Template: Use BFS queue over a matrix, often from multiple sources. Initialize queue with all starting points (e.g., rotten oranges). Process in waves (levels) to simulate spread by time steps.
Example Problem: LeetCode 994 – Rotting Oranges. Grid with fresh(1)/rotten(2)/empty(0) oranges. Rotten oranges infect adjacent fresh ones per minute. Find min minutes until no fresh remain.
- Min 0: Queue=[(0,0)]. Fresh=6. (Corrected fresh count: (0,1),(0,2),(1,0),(1,1),(2,1),(2,2)).
- Min 1: Process (0,0). Rots (0,1), (1,0). Fresh=4. Queue for next: [(0,1), (1,0)].
- Min 2: Process (0,1) -> rots (0,2), (1,1). Process (1,0) -> (no new fresh neighbors already considered from (0,1)). Fresh=2. Queue for next: [(0,2), (1,1)].
- Min 3: Process (0,2) -> no new fresh. Process (1,1) -> rots (2,1). Fresh=1. Queue for next: [(2,1)].
- Min 4: Process (2,1) -> rots (2,2). Fresh=0. Queue for next: [(2,2)].
from collections import deque
def orangesRotting(grid: list[list[int]]) -> int:
rows, cols = len(grid), len(grid[0])
q = deque()
fresh = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2: q.append((r, c))
elif grid[r][c] == 1: fresh += 1
minutes = 0
directions = [(1,0),(-1,0),(0,1),(0,-1)]
while q and fresh > 0:
for _ in range(len(q)): # Process one level (minute)
x, y = q.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] == 1:
grid[nx][ny] = 2
fresh -= 1
q.append((nx, ny))
minutes += 1
return minutes if fresh == 0 else -1
Monotonic Queue (Sliding Window Maximum)
Modified Template: Use a deque to maintain window elements (indices) in decreasing order of their values. Pop from front when element exits window; pop from back any elements smaller than the new one (as they can't be max). Deque's front is always current window max.
Example Problem: LeetCode 239 – Sliding Window Maximum. Given array and window size k, output max in each window.
- Win [1,3,-1] (idx 0-2): Deque: Push 0 (1). Pop 0, Push 1 (3). Push 2 (-1). Deque:[1,2]. Max: nums[1]=3. Result:[3].
- Win [3,-1,-3] (idx 1-3): Check dq[0]=1 (in window). Add nums[3]=-3. Deque:[1,2,3]. Max: nums[1]=3. Result:[3,3].
- Win [-1,-3,5] (idx 2-4): dq[0]=1 (out of window [2,4]), pop 1. Deque:[2,3]. Add nums[4]=5. Pop 3 (-3<5), Pop 2 (-1<5). Push 4. Deque:[4]. Max: nums[4]=5. Result:[3,3,5].
- ...and so on. Final Result: [3,3,5,5,6,7].
from collections import deque
def maxSlidingWindow(nums: list[int], k: int) -> list[int]:
dq = deque() # stores indices
result = []
for i, value in enumerate(nums):
# Remove indices out of window
if dq and dq[0] < i - k + 1: dq.popleft()
# Remove smaller values from back
while dq and nums[dq[-1]] < value: dq.pop()
dq.append(i)
if i >= k - 1: result.append(nums[dq[0]])
return result
Design: Implement Queue using Stacks
Modified Template: Use two stacks (in-stack, out-stack) to emulate FIFO queue. Push to in-stack. For pop/peek, if out-stack empty, pour all from in-stack to out-stack (reverses order).
Example Problem: LeetCode 232 – Implement Queue using Stacks.
init(): in_stk=[], out_stk=[]push(1): in_stk=[1]push(2): in_stk=[1,2]peek(): out_stk empty. Pour: out_stk=[2,1], in_stk=[]. Returns out_stk.top (1).pop(): out_stk not empty. Pops 1. out_stk=[2].empty(): False (out_stk has 2).
class MyQueue:
def __init__(self):
self.stack_in = []
self.stack_out = []
def push(self, x: int): self.stack_in.append(x)
def _transfer_if_needed(self):
if not self.stack_out:
while self.stack_in: self.stack_out.append(self.stack_in.pop())
def pop(self) -> int:
self._transfer_if_needed()
return self.stack_out.pop()
def peek(self) -> int:
self._transfer_if_needed()
return self.stack_out[-1]
def empty(self) -> bool: return not self.stack_in and not self.stack_out
Time-Based Sliding Window (Recent Calls Counter)
Modified Template: Use a queue for items within a time window (timestamps). Enqueue new events; dequeue old events outside the time frame from queue front upon query.
Example Problem: LeetCode 933 – Number of Recent Calls. ping(t) returns calls in last 3000ms.
RecentCounter(): q=[]ping(1): Add 1. q=[1]. Remove calls < 1-3000 (none). Return len(q)=1.ping(100): Add 100. q=[1,100]. Remove calls < 100-3000 (none). Return len(q)=2.ping(3001): Add 3001. q=[1,100,3001]. Remove calls < 3001-3000=1 (none, 1 is not < 1). Return len(q)=3.ping(3002): Add 3002. q=[1,100,3001,3002]. Remove calls < 3002-3000=2. Pop 1. q=[100,3001,3002]. Return len(q)=3.
from collections import deque
class RecentCounter:
def __init__(self): self.q = deque()
def ping(self, t: int) -> int:
self.q.append(t)
while self.q and self.q[0] < t - 3000: self.q.popleft()
return len(self.q)