You're in an interview. The problem mentions "maximum," "window," and "subarray." Your mind races: Is this sliding window? Monotonic stack? Dynamic programming?
This is the pattern recognition problem—and it's what separates those who solve problems in 5 minutes from those who struggle for 30. The patterns look similar on the surface, but choosing the wrong one wastes precious interview time.
This comprehensive guide will teach you the exact decision framework to recognize monotonic stack problems, distinguish them from similar patterns, and choose the right approach in under 30 seconds.
The Pattern Recognition Problem
Why Confusion Happens
Many algorithmic patterns share similar keywords:
"Maximum subarray" could be:
- Kadane's Algorithm (max sum)
- Sliding Window (max with constraint)
- Monotonic Stack (max with structure)
"Window of size k" could be:
- Sliding Window (variable condition)
- Monotonic Queue (max/min in window)
"Count subarrays" could be:
- Prefix Sum + Hash Map
- Monotonic Stack (contribution technique)
- Dynamic Programming
The Cost of Wrong Choice
Choosing the wrong pattern:
- ❌ Wastes 10-15 minutes implementing wrong solution
- ❌ Leads to TLE (Time Limit Exceeded) or WA (Wrong Answer)
- ❌ Signals poor pattern recognition to interviewer
- ❌ Creates stress and reduces confidence
Choosing the right pattern:
- ✅ Solve in 5-10 minutes
- ✅ Optimal time complexity
- ✅ Demonstrates strong fundamentals
- ✅ Leaves time for follow-ups
The 30-Second Decision Framework
Step 1: Identify the Core Question
What is the problem asking for?
┌─────────────────────────────────────────┐
│ "Find next greater/smaller element" │ → Monotonic Stack
├─────────────────────────────────────────┤
│ "Largest rectangle / histogram" │ → Monotonic Stack (Increasing)
├─────────────────────────────────────────┤
│ "Sliding window maximum/minimum" │ → Monotonic Queue
├─────────────────────────────────────────┤
│ "Maximum/minimum subarray sum" │ → Kadane's / Prefix Sum
├─────────────────────────────────────────┤
│ "Longest substring with condition" │ → Sliding Window
├─────────────────────────────────────────┤
│ "Count subarrays with sum = k" │ → Prefix Sum + Hash Map
├─────────────────────────────────────────┤
│ "Optimal subsequence" │ → Dynamic Programming
└─────────────────────────────────────────┘Step 2: Check for Monotonic Stack Keywords
🔍 Monotonic Stack Keywords:
- "Next greater element"
- "Next smaller element"
- "Previous greater/smaller"
- "Histogram"
- "Largest rectangle"
- "Stock span"
- "Visible buildings"
- "Trapping rain water"
- "Remove K digits"
If you see these → 90% chance it's monotonic stack
Step 3: Distinguish from Similar Patterns
Use this decision tree:
Does the problem involve finding next/previous greater/smaller?
├─ YES → Monotonic Stack ✓
└─ NO → Continue
Does it involve a sliding window?
├─ YES → Is it asking for max/min in each window?
│ ├─ YES → Monotonic Queue ✓
│ └─ NO → Sliding Window (two pointers) ✓
└─ NO → Continue
Does it involve optimal sum/count?
├─ Sum of subarray = k → Prefix Sum + Hash Map ✓
├─ Maximum subarray sum → Kadane's Algorithm ✓
└─ Optimal subsequence → Dynamic Programming ✓Monotonic Stack vs Sliding Window
Key Differences
| Aspect | Monotonic Stack | Sliding Window |
|---|---|---|
| Goal | Find next/previous greater/smaller | Find subarray with condition |
| Window | No fixed window | Fixed or variable window |
| Structure | Stack (LIFO) | Two pointers |
| Complexity | O(n) | O(n) |
| Space | O(n) | O(1) typically |
When to Use Each
Use Monotonic Stack:
- ✅ Finding next/previous greater/smaller element
- ✅ Histogram or rectangle problems
- ✅ Structure-based queries (what's the next larger bar?)
Use Sliding Window:
- ✅ Finding longest/shortest subarray with condition
- ✅ Contiguous elements must satisfy constraint
- ✅ Expand/shrink window based on condition
Example Comparison
Problem 1: "Find next greater element for each position"
❌ Wrong: Sliding Window
# Sliding window doesn't make sense here
# We're not looking for a contiguous subarray✅ Correct: Monotonic Stack
stack = []
for i in range(n):
while stack and nums[i] > nums[stack[-1]]:
result[stack.pop()] = nums[i]
stack.append(i)Problem 2: "Find longest subarray with sum ≤ k"
❌ Wrong: Monotonic Stack
# Stack doesn't help with contiguous sum constraint✅ Correct: Sliding Window
left = 0
current_sum = 0
for right in range(n):
current_sum += nums[right]
while current_sum > k:
current_sum -= nums[left]
left += 1
max_len = max(max_len, right - left + 1)Hybrid: Sliding Window Maximum
Problem: "Find maximum in each sliding window of size k"
Why it's confusing: Has "sliding window" in the name but uses monotonic queue!
Analysis:
- Sliding window aspect: Window of size k
- Monotonic queue aspect: Need max/min in O(1)
Solution: Monotonic Queue (Deque)
from collections import deque
dq = deque()
for i in range(n):
while dq and dq[0] < i - k + 1:
dq.popleft()
while dq and nums[i] > nums[dq[-1]]:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]])Rule: If problem asks for max/min in each window, use monotonic queue, not sliding window two pointers.
Monotonic Stack vs Dynamic Programming
Key Differences
| Aspect | Monotonic Stack | Dynamic Programming |
|---|---|---|
| Goal | Find next/previous extremes | Optimal subsequence/subarray |
| Recurrence | No recurrence relation | Has recurrence relation |
| Dependencies | Independent elements | Depends on previous states |
| Typical Use | Next greater, histogram | Longest increasing subsequence |
When to Use Each
Use Monotonic Stack:
- ✅ Finding structural properties (next greater/smaller)
- ✅ No optimal substructure needed
- ✅ Linear scan with stack maintenance
Use Dynamic Programming:
- ✅ Optimal substructure exists
- ✅ Overlapping subproblems
- ✅ Need to build solution from smaller problems
Example: Sum of Subarray Minimums
Problem: Find sum of minimums of all subarrays.
Naive DP: O(n²)
# dp[i] = sum of minimums of all subarrays ending at i
for i in range(n):
min_val = nums[i]
for j in range(i, -1, -1):
min_val = min(min_val, nums[j])
total += min_val
# Time: O(n²)Optimized with Monotonic Stack: O(n)
# For each element, find how many subarrays it's the minimum
# Use contribution technique with monotonic stack
left, right = find_boundaries(nums) # O(n) with stack
for i in range(n):
count = (i - left[i]) * (right[i] - i)
total += nums[i] * count
# Time: O(n)Key insight: Monotonic stack optimizes certain DP problems by finding boundaries in O(n).
When Stack Optimizes DP
Pattern: When DP transition requires finding min/max in a range
Example problems:
- Sum of Subarray Minimums (#907)
- Sum of Subarray Ranges (#2104)
- Constrained Subsequence Sum (#1425) - uses monotonic queue
Rule: If DP transition is O(k) and involves range min/max, consider monotonic stack/queue optimization.
Monotonic Stack vs Two Pointers
Key Differences
| Aspect | Monotonic Stack | Two Pointers |
|---|---|---|
| Array Type | Any array | Usually sorted |
| Goal | Next greater/smaller | Find pairs/triplets |
| Movement | Stack push/pop | Pointers move based on condition |
| Space | O(n) | O(1) |
When to Use Each
Use Monotonic Stack:
- ✅ Unsorted array
- ✅ Finding next/previous greater/smaller
- ✅ Structural queries
Use Two Pointers:
- ✅ Sorted array (or can sort)
- ✅ Finding pairs that meet condition
- ✅ Opposite direction or same direction movement
Example: Container With Most Water
Problem: Find two lines that form container with maximum water.
Why it's confusing: Involves finding maximum, but uses two pointers!
Analysis:
- Array is not sorted by height
- But we can use greedy two pointers
- Not about "next greater"—about optimal pair
Solution: Two Pointers (Opposite Direction)
left, right = 0, len(height) - 1
max_area = 0
while left < right:
area = (right - left) * min(height[left], height[right])
max_area = max(max_area, area)
if height[left] < height[right]:
left += 1
else:
right -= 1Rule: If problem involves pairs and greedy decisions, consider two pointers even if array is unsorted.
Monotonic Stack vs Priority Queue
Key Differences
| Aspect | Monotonic Stack | Priority Queue |
|---|---|---|
| Complexity | O(n) | O(n log n) or O(n log k) |
| Use Case | Next greater/smaller | General min/max queries |
| Ordering | Monotonic (strict) | Heap property |
| Removal | LIFO | Min/max element |
When to Use Each
Use Monotonic Stack/Queue:
- ✅ Next/previous greater/smaller
- ✅ Sliding window max/min
- ✅ Want O(n) complexity
Use Priority Queue:
- ✅ K-th largest/smallest
- ✅ Merge k sorted arrays
- ✅ Median finding
- ✅ Don't need optimal O(n)
Example: Sliding Window Maximum
Priority Queue: O(n log k)
import heapq
heap = []
for i in range(n):
heapq.heappush(heap, (-nums[i], i))
while heap[0][1] < i - k + 1:
heapq.heappop(heap)
if i >= k - 1:
result.append(-heap[0][0])
# Time: O(n log k)Monotonic Queue: O(n)
from collections import deque
dq = deque()
for i in range(n):
while dq and dq[0] < i - k + 1:
dq.popleft()
while dq and nums[i] > nums[dq[-1]]:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]])
# Time: O(n)Rule: For sliding window max/min, always use monotonic queue over priority queue.
See detailed comparison: Deque vs Priority Queue
Decision Tree: Complete Framework
┌─────────────────────────────────────────────────────────┐
│ STEP 1: What's the core question? │
└─────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
v v v
"Next greater" "Max in window" "Longest subarray"
│ │ │
v v v
Monotonic Stack Monotonic Queue Sliding Window
│ │ │
└───────────────┴───────────────┘
│
v
┌─────────────────────────────────────────────────────────┐
│ STEP 2: Check array properties │
└─────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
v v v
Sorted? Fixed window? Need pairs?
│ │ │
v v v
Two Pointers Monotonic Queue Two Pointers
│ │ │
└───────────────┴───────────────┘
│
v
┌─────────────────────────────────────────────────────────┐
│ STEP 3: Check for optimal substructure │
└─────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
v v v
Recurrence? Sum = k? Subsequence?
│ │ │
v v v
DP Prefix Sum + Hash DPQuick Recognition Checklist
✅ Use Monotonic Stack When You See:
-
"Next greater element"
-
"Next smaller element"
-
"Previous greater/smaller"
-
"Histogram"
-
"Largest rectangle"
-
"Stock span"
-
"Visible buildings"
-
"Trapping rain water"
-
"Remove K digits"
-
"Buildings with ocean view"
✅ Use Monotonic Queue When You See:
-
"Sliding window maximum"
-
"Sliding window minimum"
-
"Maximum in each window of size k"
-
"Constrained subsequence" (DP + queue)
✅ Use Sliding Window When You See:
-
"Longest substring with..."
-
"Shortest subarray with..."
-
"Maximum sum subarray with constraint"
-
"At most K distinct characters"
-
"Minimum window substring"
✅ Use Two Pointers When You See:
-
"Sorted array" + "find pairs"
-
"Two Sum II" (sorted)
-
"3Sum" (can sort)
-
"Container with most water"
-
"Trapping rain water" (can use two pointers OR stack)
✅ Use DP When You See:
-
"Longest increasing subsequence"
-
"Maximum profit" (stocks)
-
"Minimum cost"
-
"Count ways to..."
-
"Optimal subsequence"
✅ Use Prefix Sum When You See:
-
"Subarray sum equals k"
-
"Continuous subarray sum"
-
"Range sum query"
Common Misidentifications
Misidentification 1: Trapping Rain Water
Problem: Calculate water trapped between bars.
Confusion: Could be two pointers OR monotonic stack!
Both work:
Two Pointers: O(n) time, O(1) space
left, right = 0, len(height) - 1
left_max = right_max = water = 0
while left < right:
if height[left] < height[right]:
left_max = max(left_max, height[left])
water += left_max - height[left]
left += 1
else:
right_max = max(right_max, height[right])
water += right_max - height[right]
right -= 1Monotonic Stack: O(n) time, O(n) space
stack = []
water = 0
for i in range(len(height)):
while stack and height[i] > height[stack[-1]]:
bottom = stack.pop()
if not stack:
break
width = i - stack[-1] - 1
bounded_height = min(height[i], height[stack[-1]]) - height[bottom]
water += width * bounded_height
stack.append(i)Rule: When multiple patterns work, choose based on:
- Space constraint (two pointers uses O(1))
- Clarity (stack is more intuitive for some)
- Interview context (demonstrate pattern knowledge)
Misidentification 2: Maximum Subarray Sum
Problem: Find maximum sum of contiguous subarray.
Confusion: Sliding window? DP? Monotonic stack?
Correct: Kadane's Algorithm (DP)
max_sum = current_sum = nums[0]
for num in nums[1:]:
current_sum = max(num, current_sum + num)
max_sum = max(max_sum, current_sum)Why not sliding window? Window size is variable, but we're not maintaining a constraint—we're finding optimal sum.
Why not monotonic stack? Not about "next greater"—about optimal sum.
Misidentification 3: Longest Substring Without Repeating
Problem: Find longest substring without repeating characters.
Confusion: Monotonic stack? Two pointers?
Correct: Sliding Window (with hash set)
seen = set()
left = max_len = 0
for right in range(len(s)):
while s[right] in seen:
seen.remove(s[left])
left += 1
seen.add(s[right])
max_len = max(max_len, right - left + 1)Why not monotonic stack? Not about "next greater"—about longest valid window.
Practice: Pattern Recognition
Exercise 1: Identify the Pattern
For each problem, identify the correct pattern in 30 seconds:
Problem 1: "Given an array, for each element, find the next element that is greater than it."
Answer
Pattern: Monotonic Decreasing Stack
Keyword: "next element that is greater"
Time: O(n)
Problem 2: "Find the longest substring with at most K distinct characters."
Answer
Pattern: Sliding Window
Keyword: "longest substring with constraint"
Time: O(n)
Problem 3: "Find the maximum value in each sliding window of size K."
Answer
Pattern: Monotonic Queue (Deque)
Keyword: "maximum in each window"
Time: O(n)
Problem 4: "Count the number of subarrays with sum equal to K."
Answer
Pattern: Prefix Sum + Hash Map
Keyword: "count subarrays with sum"
Time: O(n)
Problem 5: "Find the largest rectangle in a histogram."
Answer
Pattern: Monotonic Increasing Stack
Keyword: "largest rectangle" + "histogram"
Time: O(n)
Problem 6: "Given a sorted array, find two numbers that sum to target."
Answer
Pattern: Two Pointers (Opposite Direction)
Keyword: "sorted array" + "find two numbers"
Time: O(n)
Problem 7: "Find the longest increasing subsequence."
Answer
Pattern: Dynamic Programming (or DP + Binary Search)
Keyword: "longest increasing subsequence"
Time: O(n²) or O(n log n)
Problem 8: "Calculate the sum of minimums of all subarrays."
Answer
Pattern: Monotonic Increasing Stack (Contribution Technique)
Keyword: "sum of minimums" + "all subarrays"
Time: O(n)
Problem 9: "Find the minimum window substring containing all characters."
Answer
Pattern: Sliding Window
Keyword: "minimum window" + "substring"
Time: O(n)
Problem 10: "Find the K-th largest element in an array."
Answer
Pattern: Priority Queue (Heap) or Quickselect
Keyword: "K-th largest"
Time: O(n log k) or O(n) average
Summary Table
| Problem Type | Pattern | Time | Space | Keywords |
|---|---|---|---|---|
| Next greater/smaller | Monotonic Stack | O(n) | O(n) | "next greater", "histogram" |
| Sliding window max/min | Monotonic Queue | O(n) | O(k) | "window maximum", "size k" |
| Longest substring | Sliding Window | O(n) | O(1) | "longest", "substring" |
| Find pairs (sorted) | Two Pointers | O(n) | O(1) | "sorted", "two numbers" |
| Subarray sum = k | Prefix Sum + Hash | O(n) | O(n) | "subarray sum", "equals k" |
| Optimal subsequence | Dynamic Programming | O(n²) | O(n) | "longest", "subsequence" |
| K-th largest | Priority Queue | O(n log k) | O(k) | "K-th", "largest" |
Conclusion
Pattern recognition is a learnable skill. With this decision framework, you can identify the correct pattern in under 30 seconds.
The 30-second checklist:
- Identify keywords: "next greater" → stack, "window max" → queue, "longest substring" → sliding window
- Check array properties: Sorted → two pointers, unsorted → stack/hash map
- Verify pattern fit: Does the template match the problem structure?
Key distinctions:
- Monotonic Stack: Next/previous greater/smaller, histogram
- Monotonic Queue: Sliding window max/min
- Sliding Window: Longest/shortest substring with constraint
- Two Pointers: Pairs in sorted array
- DP: Optimal subsequence with recurrence
Practice strategy:
- Solve 3-5 problems from each pattern
- For each problem, identify pattern BEFORE coding
- Time yourself: can you identify in 30 seconds?
- Review misidentifications and understand why
Master pattern recognition, and you'll solve interview problems with confidence. For deep dives into specific patterns:
Next time you see a problem, ask: "What's the core question?" The answer will guide you to the right pattern.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
