Hello Everyone! π
I'm Rajat, a 3rd-year CS student, and let me be honest - LeetCode kicked my ass for months.
I'd open an problem, stare at it for 30 minutes, give up, look at the solution, and think "how was I supposed to think of that??" π
Sound familiar?
Here's what changed everything: I stopped grinding random problems and started learning PATTERNS.
π€ What I Wish Someone Told Me on Day 1
Wrong approach (what I did):
- Solve problem #1
- Solve problem #2
- Solve problem #3
- Each one feels brand new π°
- Forget everything by next week
Right approach (what actually works):
- Learn Pattern #1
- Solve 5 problems using Pattern #1
- Brain recognizes the pattern
- New problems become variations of known patterns
- Actually retain what you learned! π§
The truth: LeetCode has ~3000 problems, but only ~15 core patterns.
Master the patterns β Solve most problems β¨
π What This Guide Is
Full transparency:
- I'm NOT at FAANG (yet! π€)
- I've solved ~200 problems (not 2000)
- I still look up solutions sometimes
- BUT I finally understand HOW to approach problems
What you'll get:
- β 6 essential patterns (Part 1 of 2)
- β The intuition behind each pattern
- β When to use (and when NOT to use)
- β Real LeetCode problems explained
- β Complete code solutions
- β Common mistakes I made (so you don't!)
- β Student-friendly explanations (no jargon!)
Time investment: One weekend to learn
Difficulty: Beginner-friendly (I explain everything!)
Cost: $0 (LeetCode free tier is enough)
Note: This is Part 1 (Patterns 1-6). Part 2 coming next week with patterns 7-15!
π Table of Contents
- Pattern 1: Prefix Sum
- Pattern 2: Two Pointers
- Pattern 3: Sliding Window
- Pattern 4: Fast & Slow Pointers
- Pattern 5: LinkedList In-Place Reversal
- Pattern 6: Monotonic Stack
- How to Practice
- Common Mistakes
π― Pattern 1: Prefix Sum
The Intuition (How I Finally Got It)
Picture this: You're at a restaurant tracking your expenses every day. Without prefix sum, every time your friend asks "how much did we spend from Monday to Wednesday?", you're adding numbers again. Exhausting, right?
The lightbulb moment for me: What if I just wrote down running totals?
Day 1: $10 (total so far: $10)
Day 2: $15 (total so far: $25)
Day 3: $20 (total so far: $45)
Now ANY question is instant subtraction! "Monday to Wednesday?" β $45 - $0 = $45. Done. β¨
Core insight: Do the work ONCE upfront, answer infinite queries instantly.
What Even Is It?
Simple explanation: Pre-calculate all the sums so you don't have to recalculate them every time.
Real-world analogy:
Imagine you're tracking your daily expenses:
- Day 1: $10
- Day 2: $15
- Day 3: $20
Without prefix sum (slow way):
"How much did I spend from Day 1 to Day 2?"
β Calculate: 10 + 15 = 25
"How much from Day 1 to Day 3?"
β Calculate again: 10 + 15 + 20 = 45
With prefix sum (smart way):
Pre-calculate running totals once:
- Prefix[1] = 10
- Prefix[2] = 25 (10+15)
- Prefix[3] = 45 (10+15+20)
Now any range query is instant! Just subtraction.
When to Use This Pattern
Signs you need prefix sum:
- β Problem asks for sum of subarray/range
- β Multiple queries on the same array
- β Words like "range sum", "cumulative sum"
- β Need to answer queries efficiently
Real clue from problem: "Given an array, answer Q queries about sum in range [i, j]"
When NOT to use:
- β Only one query (not worth the preprocessing)
- β Streaming data (can't preprocess what you haven't seen)
- β Array changes frequently (prefix becomes invalid)
Core Concept
# Build prefix sum array
def build_prefix_sum(nums):
prefix = [0] # Start with 0 for easier calculation
for num in nums:
prefix.append(prefix[-1] + num)
return prefix
# Example
nums = [1, 2, 3, 4, 5]
prefix = [0, 1, 3, 6, 10, 15]
# β β β β β β
# 0 1 1+2 1+2+3 ...
# Get sum from index i to j (0-indexed)
def range_sum(prefix, i, j):
return prefix[j + 1] - prefix[i]
# Sum from index 1 to 3 (2+3+4)
result = prefix[4] - prefix[1] # 10 - 1 = 9 β
LeetCode Problem 1: Range Sum Query - Immutable (#303)
Problem: Given an array, answer multiple sum queries efficiently.
Example:
Input: nums = [1, 2, 3, 4, 5]
Queries:
- sumRange(0, 2) β 1+2+3 = 6
- sumRange(1, 4) β 2+3+4+5 = 14
My Solution (Python):
class NumArray:
def __init__(self, nums):
"""
Build prefix sum array once during initialization.
This is the "preprocessing" step!
"""
# Start with [0] for easier calculation
self.prefix = [0]
# Build running sum
for num in nums:
self.prefix.append(self.prefix[-1] + num)
# Example: nums = [1, 2, 3, 4, 5]
# prefix = [0, 1, 3, 6, 10, 15]
def sumRange(self, left, right):
"""
Get sum from left to right in O(1) time!
Magic formula: prefix[right + 1] - prefix[left]
"""
return self.prefix[right + 1] - self.prefix[left]
# Usage
obj = NumArray([1, 2, 3, 4, 5])
print(obj.sumRange(0, 2)) # Output: 6
print(obj.sumRange(1, 4)) # Output: 14
Why this works:
nums = [1, 2, 3, 4, 5]
prefix = [0, 1, 3, 6, 10, 15]
β β β
0 left right+1
Sum from index 1 to 3:
= prefix[4] - prefix[1]
= 10 - 1
= 9 (which is 2+3+4) β
Time Complexity:
- Build prefix: O(n)
- Each query: O(1) (instant!)
- Without prefix: Each query would be O(n) π±
Common Mistake #1: Forgetting the initial 0
# I did this and spent an HOUR debugging! π€¦
prefix = [] # WRONG - crashes on first element!
for num in nums:
prefix.append(prefix[-1] + num) # IndexError!
# RIGHT β
prefix = [0] # This 0 is your best friend!
The 0 represents "sum before any elements" - it's not random! Think of it like asking "how much money before you earned anything?" The answer is $0. This base case makes everything work smoothly.
LeetCode Problem 2: Contiguous Array (#525)
The Intuition: When I first saw "equal 0s and 1s", I thought "count them in every subarray?" - that's O(nΒ²), way too slow!
Then it hit me: What if 0s and 1s cancelled each other out? Like +1 and -1 in math. If they're equal, sum = 0! π€―
This transforms "equal count" into "sum = 0", which we KNOW how to solve with prefix sum!
Problem: Find longest subarray with equal number of 0s and 1s.
Example:
Input: [0, 1, 0]
Output: 2 (subarray [0, 1] or [1, 0])
Key insight: Treat 0 as -1, then find subarray with sum = 0!
My Solution:
def findMaxLength(nums):
"""
Trick: Replace 0 with -1, then find longest subarray with sum 0
[0, 1, 0] β [-1, 1, -1]
If prefix sum appears twice, the subarray between them has sum 0!
"""
# Track: prefix_sum β first index where we saw it
seen = {0: -1} # Base case: sum 0 at index -1
max_len = 0
prefix_sum = 0
for i, num in enumerate(nums):
# Treat 0 as -1
prefix_sum += 1 if num == 1 else -1
# Have we seen this sum before?
if prefix_sum in seen:
# Length of subarray with sum 0
length = i - seen[prefix_sum]
max_len = max(max_len, length)
else:
# First time seeing this sum
seen[prefix_sum] = i
return max_len
Visual walkthrough:
nums = [0, 1, 1, 0]
as -1/1: [-1, 1, 1, -1]
prefix_sum: -1, 0, 1, 0
β β β
i=0 i=2 i=3
At i=1: sum=0 (seen at i=-1), length = 1-(-1) = 2
At i=3: sum=0 (seen at i=1), length = 3-1 = 2
Common Mistake #2: The mysterious {0: -1}
# I forgot this and couldn't figure out edge cases for DAYS!
seen = {} # WRONG - misses subarrays starting from index 0
# RIGHT β
seen = {0: -1} # -1 means "before the array starts"
Think of -1 as the "starting line" in a race. When you see sum=0 at index 3, you calculate length = 3 - (-1) = 4. It's the number of steps from the start!
LeetCode Problem 3: Subarray Sum Equals K (#560)
The Intuition: This one broke my brain at first. Then I realized: if I want sum = k, I need to find where current_sum - k happened before. It's like reverse engineering!
If I'm at position with sum=10, and I want k=3, I look for where sum was 7. The part between 7 and 10? That's exactly 3! π―
Problem: Count number of subarrays with sum = k.
Example:
Input: nums = [1, 2, 3], k = 3
Output: 2 (subarrays: [1,2] and [3])
My Solution:
def subarraySum(nums, k):
"""
Key insight: If prefix_sum[j] - prefix_sum[i] = k
Then subarray from i to j has sum k!
Rearranging: prefix_sum[i] = prefix_sum[j] - k
"""
# Track: prefix_sum β how many times we've seen it
seen = {0: 1} # Base case
count = 0
prefix_sum = 0
for num in nums:
prefix_sum += num
# Check if (prefix_sum - k) exists
# If yes, we found subarray(s) with sum k!
if (prefix_sum - k) in seen:
count += seen[prefix_sum - k]
# Update frequency
seen[prefix_sum] = seen.get(prefix_sum, 0) + 1
return count
Why this works:
nums = [1, 2, 3], k = 3
prefix_sum: 1, 3, 6
β β
At i=1: prefix=3, check if (3-3=0) exists?
Yes! Count = 1 (subarray [1,2])
At i=2: prefix=6, check if (6-3=3) exists?
Yes! Count = 2 (subarray [3])
Common Mistake #3: Using set instead of counter
# I did this and got wrong answers! π€¦
seen = set([0]) # WRONG - only tracks existence
if target in seen:
count += 1 # Always adds 1, even if target appeared multiple times!
# RIGHT β
seen = {0: 1} # Tracks frequency
if target in seen:
count += seen[target] # Adds correct count!
Why frequency matters: If the same sum appeared 3 times before, we found 3 different subarrays! Don't lose that information.
π― Pattern 2: Two Pointers
The Intuition (My "Aha!" Moment)
Imagine two people at opposite ends of a hallway trying to find each other. If they both walk toward the middle, they'll meet WAY faster than one person checking every spot, right?
That's two pointers! We eliminate half the possibilities with each decision.
The genius: When sum is too small, we KNOW we need a bigger number (move left). When sum is too big, we KNOW we need a smaller number (move right). We're not guessing - we're intelligently shrinking the search space.
What Even Is It?
Simple explanation: Use two pointers moving toward each other (or same direction) to solve problems.
Real-world analogy:
Imagine two people searching for each other in a hallway:
- Person A starts at left end
- Person B starts at right end
- They walk toward each other until they meet
Much faster than one person checking every spot! πΆββοΈπΆββοΈ
When to Use This Pattern
Signs you need two pointers:
- β Sorted array (or can be sorted)
- β Need to find pairs/triplets
- β Need to compare elements from both ends
- β Problem mentions "opposite ends" or "pairs"
When NOT to use:
- β Order matters and array can't be sorted
- β Need to check ALL combinations (not just pairs)
- β Elements don't have a comparison relationship
Core Concept
# Two pointers approaching from opposite ends
def two_pointer_opposite(nums, target):
left = 0
right = len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left, right] # Found it!
elif current_sum < target:
left += 1 # Need bigger sum
else:
right -= 1 # Need smaller sum
return [-1, -1] # Not found
LeetCode Problem 1: Two Sum II (#167)
The Intuition: Since the array is sorted, we get a superpower: We can make smart decisions about which pointer to move. Too small? Move left (get bigger). Too big? Move right (get smaller). We're not randomly trying - we're eliminating wrong answers!
Problem: Find two numbers in SORTED array that add to target.
Example:
Input: numbers = [2, 7, 11, 15], target = 9
Output: [1, 2] (indices 1 and 2, 1-indexed)
My Solution:
def twoSum(numbers, target):
"""
Since array is SORTED, we can use two pointers!
Key insight:
- If sum < target, left pointer moves right (increase sum)
- If sum > target, right pointer moves left (decrease sum)
"""
left = 0
right = len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
# Found it! (1-indexed)
return [left + 1, right + 1]
elif current_sum < target:
# Need larger sum, move left pointer right
left += 1
else:
# Need smaller sum, move right pointer left
right -= 1
return [-1, -1]
Visual walkthrough:
numbers = [2, 7, 11, 15], target = 9
β β
left right
Step 1: 2 + 15 = 17 > 9, move right left
Step 2: 2 + 11 = 13 > 9, move right left
Step 3: 2 + 7 = 9 β
Found it!
Common Mistake #4: Forgetting 1-indexed output
# I returned [0, 1] and spent 20 minutes wondering why it failed! π€¦
return [left, right] # WRONG - problem wants 1-indexed!
# RIGHT β
return [left + 1, right + 1] # Always read the problem carefully!
LeetCode Problem 2: 3Sum (#15)
The Intuition: This is just Two Sum... but 3 times! Fix one number, then use two pointers for the remaining two. The trick: skip duplicates aggressively, or you'll get the same triplet multiple times.
Problem: Find all unique triplets that sum to zero.
Example:
Input: nums = [-1, 0, 1, 2, -1, -4]
Output: [[-1, -1, 2], [-1, 0, 1]]
My Solution:
def threeSum(nums):
"""
3Sum = Fix one number + Two Sum on remaining
Steps:
1. Sort array
2. Fix first number
3. Use two pointers for remaining two
4. Skip duplicates!
"""
nums.sort() # MUST sort first!
result = []
for i in range(len(nums) - 2):
# Skip duplicates for first number
if i > 0 and nums[i] == nums[i - 1]:
continue
# Two pointers for remaining array
left = i + 1
right = len(nums) - 1
target = -nums[i]
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
result.append([nums[i], nums[left], nums[right]])
# Skip duplicates for second number
while left < right and nums[left] == nums[left + 1]:
left += 1
# Skip duplicates for third number
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif current_sum < target:
left += 1
else:
right -= 1
return result
Common Mistake #5: Not skipping duplicates
# My first solution had duplicate triplets everywhere! π€¦
# WRONG - no duplicate handling
for i in range(len(nums) - 2):
# No skip here
left = i + 1
...
# RIGHT β
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue # Skip duplicates!
Without skipping: Input [-1, -1, 2] gives you [[-1, -1, 2], [-1, -1, 2]]. With skipping: Just [[-1, -1, 2]]. Clean!
LeetCode Problem 3: Container With Most Water (#11)
The Intuition: Start with maximum width. Now, the height is limited by the shorter line. If we move the shorter line, height MIGHT increase. If we move the taller line, height can ONLY stay same or decrease (limited by the other line). Always move the shorter line - it's our bottleneck!
Problem: Find two lines that contain the most water.
My Solution:
def maxArea(height):
"""
Key insight:
Area = width Γ min(left_height, right_height)
Start with maximum width, then try to find taller lines
"""
left = 0
right = len(height) - 1
max_water = 0
while left < right:
# Calculate current area
width = right - left
current_height = min(height[left], height[right])
current_area = width * current_height
max_water = max(max_water, current_area)
# Move the pointer with smaller height
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_water
Why move smaller height?
height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
β β
left=1 right=7
If we move right pointer:
- Width decreases
- Height still limited by left=1
- Area can only get WORSE!
If we move left pointer:
- Width decreases
- But height might increase!
- Chance of better area β
π― Pattern 3: Sliding Window
The Intuition (The Train Window Analogy)
Imagine you're on a train looking out a window. As the train moves:
- New tree enters your view (add to window)
- Old tree exits your view (remove from window)
- You don't recount ALL trees each time - just update!
That's sliding window! Instead of recalculating from scratch, we adjust what we already know.
What Even Is It?
Simple explanation: Maintain a "window" of elements and slide it through the array.
When to Use This Pattern
Signs you need sliding window:
- β Contiguous subarray/substring
- β "Maximum/minimum of subarray of size k"
- β "Longest substring with condition"
- β Need to track elements in a range
When NOT to use:
- β Non-contiguous elements (use dp or backtracking)
- β Need global information (not just local window)
Core Concept
# Fixed size window
def fixed_window(nums, k):
window_sum = sum(nums[:k]) # Initial window
max_sum = window_sum
for i in range(k, len(nums)):
# Slide window: remove left, add right
window_sum = window_sum - nums[i - k] + nums[i]
max_sum = max(max_sum, window_sum)
return max_sum
LeetCode Problem 1: Maximum Average Subarray I (#643)
The Intuition: Instead of summing 4 elements every time (slow!), what if we just remove the element leaving the window and add the element entering? It's like a sliding belt - same size, just moving! O(n Γ k) becomes O(n).
Problem: Find contiguous subarray of size k with maximum average.
My Solution:
def findMaxAverage(nums, k):
"""
Fixed size sliding window
Instead of recalculating sum each time:
- Remove leftmost element
- Add rightmost element
"""
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
window_sum = window_sum - nums[i - k] + nums[i]
max_sum = max(max_sum, window_sum)
return max_sum / k
Common Mistake #6: Recalculating sum each time
# This is O(n Γ k) - TOO SLOW! π
for i in range(len(nums) - k + 1):
current_sum = sum(nums[i:i+k]) # WRONG - summing every time!
# RIGHT β
- O(n)
window_sum = sum(nums[:k]) # Sum once
for i in range(k, len(nums)):
window_sum = window_sum - nums[i-k] + nums[i] # Just adjust!
LeetCode Problem 2: Longest Substring Without Repeating (#3)
The Intuition: Expand the window greedily until you hit a duplicate. Then shrink from the left until the duplicate is gone. It's like a caterpillar inching forward! π
Problem: Find longest substring without repeating characters.
My Solution:
def lengthOfLongestSubstring(s):
"""
Variable size sliding window
Expand window until duplicate
Then shrink from left until no duplicate
"""
char_set = set()
left = 0
max_length = 0
for right in range(len(s)):
# If duplicate, shrink window from left
while s[right] in char_set:
char_set.remove(s[left])
left += 1
char_set.add(s[right])
max_length = max(max_length, right - left + 1)
return max_length
Common Mistake #7: Using list instead of set
# I did this and got O(nΒ²) runtime! π€¦
chars = [] # WRONG - checking "in list" is O(n)
if s[right] in chars: # This is slow!
# RIGHT β
char_set = set() # Checking "in set" is O(1)
if s[right] in char_set: # Fast!
LeetCode Problem 3: Minimum Window Substring (#76)
The Intuition: This one's tough. Expand until you have all required characters. Then shrink as much as possible while staying valid. Track the minimum window you find. It's like finding the tightest fit!
Problem: Find minimum window containing all characters of t.
My Solution:
def minWindow(s, t):
"""
1. Expand window until all characters found
2. Shrink window while still valid
3. Track minimum window
"""
if not s or not t:
return ""
need = {}
for char in t:
need[char] = need.get(char, 0) + 1
have = {}
left = 0
min_len = float('inf')
min_start = 0
required = len(need)
formed = 0
for right in range(len(s)):
char = s[right]
have[char] = have.get(char, 0) + 1
if char in need and have[char] == need[char]:
formed += 1
# Try to shrink while valid
while formed == required:
if right - left + 1 < min_len:
min_len = right - left + 1
min_start = left
left_char = s[left]
have[left_char] -= 1
if left_char in need and have[left_char] < need[left_char]:
formed -= 1
left += 1
return "" if min_len == float('inf') else s[min_start:min_start + min_len]
This problem took me 3 days to understand. Don't feel bad if it doesn't click immediately! π
π― Pattern 4: Fast & Slow Pointers
The Intuition (The Race Track)
Two runners on a circular track: one slow, one fast. If the track loops back (cycle), the fast runner will lap the slow one - they'll meet! If the track ends, the fast runner finishes first - no cycle.
This is Floyd's algorithm, but I think of it as "the race detective" π
What Even Is It?
Simple explanation: Two pointers moving at different speeds (one fast, one slow).
When to Use This Pattern
Signs you need fast & slow:
- β Detect cycles
- β Find middle of linked list
- β Problems on linked lists with potential cycles
When NOT to use:
- β Array problems (usually two pointers work better)
- β Need exact position information (not just cycle detection)
LeetCode Problem 1: Linked List Cycle (#141)
The Intuition: If there's a cycle, fast eventually catches slow (runners on circular track). If there's no cycle, fast reaches the end (track has an end). Beautiful, right? β¨
My Solution:
def hasCycle(head):
"""
Fast moves 2 steps, slow moves 1 step
If cycle exists, they'll meet
"""
if not head:
return False
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
Common Mistake #8: Not checking fast.next
# This crashes with AttributeError! π€¦
while fast: # WRONG - need to check fast.next too!
fast = fast.next.next # Might be None!
# RIGHT β
while fast and fast.next:
fast = fast.next.next
LeetCode Problem 2: Happy Number (#202)
The Intuition: Either the number reaches 1 (happy!) or it enters a cycle (not happy). Instead of tracking all seen numbers, use fast & slow to detect the cycle! Same race track idea.
My Solution:
def isHappy(n):
def get_next(num):
total = 0
while num > 0:
digit = num % 10
total += digit ** 2
num //= 10
return total
slow = n
fast = n
while True:
slow = get_next(slow)
fast = get_next(get_next(fast))
if fast == 1:
return True
if slow == fast:
return False
LeetCode Problem 3: Find the Duplicate Number (#287)
The Intuition: Mind-bending one! Treat the array like a linked list where nums[i] points to nums[nums[i]]. If there's a duplicate, there's a cycle! Use cycle detection to find where the cycle starts (the duplicate).
My Solution:
def findDuplicate(nums):
# Phase 1: Detect cycle
slow = nums[0]
fast = nums[0]
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
# Phase 2: Find entrance (duplicate)
slow = nums[0]
while slow != fast:
slow = nums[slow]
fast = nums[fast]
return slow
This one's genius but confusing! The math behind it is Floyd's cycle detection. Trust the algorithm! π
π― Pattern 5: LinkedList In-Place Reversal
The Intuition (The Hand-Holding Flip)
Imagine people holding hands in a line: AβBβCβD
To reverse: Make B let go of C and grab A instead. Then C let go of D and grab B. One by one, change who's holding whose hand. No need for a second line of people!
What Even Is It?
Simple explanation: Reverse linked list by changing pointers, without extra space.
When to Use This Pattern
Signs you need in-place reversal:
- β "Reverse linked list"
- β "Reverse from position m to n"
- β Must be O(1) space
LeetCode Problem 1: Reverse Linked List (#206)
The Intuition: Keep three pointers (prev, curr, next). Break the forward link, create backward link, move forward. Like dominoes falling backward!
My Solution:
def reverseList(head):
prev = None
curr = head
while curr:
next_temp = curr.next # Save next
curr.next = prev # Reverse
prev = curr # Move prev
curr = next_temp # Move curr
return prev
Common Mistake #9: Forgetting to save next
# I lost track of the rest of the list! π€¦
curr.next = prev # WRONG - we just lost the rest of the list!
# RIGHT β
next_temp = curr.next # Save first!
curr.next = prev
LeetCode Problem 2: Reverse Linked List II (#92)
Problem: Reverse from position m to n.
My Solution:
def reverseBetween(head, m, n):
if not head or m == n:
return head
dummy = ListNode(0)
dummy.next = head
pre = dummy
# Find position m-1
for _ in range(m - 1):
pre = pre.next
# Reverse from m to n
curr = pre.next
for _ in range(n - m):
temp = curr.next
curr.next = temp.next
temp.next = pre.next
pre.next = temp
return dummy.next
The dummy node trick makes edge cases SO much easier! It's like having a safety net.
LeetCode Problem 3: Swap Nodes in Pairs (#24)
My Solution:
def swapPairs(head):
dummy = ListNode(0)
dummy.next = head
prev = dummy
while prev.next and prev.next.next:
first = prev.next
second = prev.next.next
# Swap
first.next = second.next
second.next = first
prev.next = second
prev = first
return dummy.next
π― Pattern 6: Monotonic Stack
The Intuition (The Height Police)
Imagine stacking books by height. New short book? Remove all taller books first to maintain order. The stack "polices" the height requirement!
When we pop a book, we found its "next taller" or "next shorter" element. That's the magic - the popping IS the answer!
What Even Is It?
Simple explanation: Stack that maintains increasing or decreasing order.
When to Use This Pattern
Signs you need monotonic stack:
- β "Next greater/smaller element"
- β "Previous greater/smaller"
- β Histogram/stock span problems
LeetCode Problem 1: Next Greater Element I (#496)
The Intuition: As we see each number, check if it's greater than numbers waiting in the stack. If yes, we found their "next greater"! Pop them and record.
My Solution:
def nextGreaterElement(nums1, nums2):
next_greater = {}
stack = []
for num in nums2:
while stack and num > stack[-1]:
smaller = stack.pop()
next_greater[smaller] = num
stack.append(num)
for num in stack:
next_greater[num] = -1
return [next_greater[num] for num in nums1]
Common Mistake #10: Storing values instead of indices
# Sometimes you need indices, not values!
stack.append(num) # WRONG if you need position!
# RIGHT β
stack.append(i) # Store index!
LeetCode Problem 2: Daily Temperatures (#739)
My Solution:
def dailyTemperatures(temperatures):
n = len(temperatures)
result = [0] * n
stack = [] # Store indices
for i in range(n):
while stack and temperatures[i] > temperatures[stack[-1]]:
prev_day = stack.pop()
result[prev_day] = i - prev_day
stack.append(i)
return result
LeetCode Problem 3: Largest Rectangle in Histogram (#84)
My Solution:
def largestRectangleArea(heights):
stack = []
max_area = 0
heights.append(0) # Sentinel
for i in range(len(heights)):
while stack and heights[i] < heights[stack[-1]]:
h_idx = stack.pop()
h = heights[h_idx]
w = i if not stack else i - stack[-1] - 1
max_area = max(max_area, h * w)
stack.append(i)
return max_area
This one's the hardest! Don't worry if it takes multiple attempts. πͺ
π How to Practice These Patterns
Week 1-2: Foundation
- [ ] Pattern 1 (Prefix Sum): 3 problems
- [ ] Pattern 2 (Two Pointers): 3 problems
- [ ] Focus on understanding WHY, not memorizing
Week 3-4: Build Momentum
- [ ] Pattern 3 (Sliding Window): 3 problems
- [ ] Pattern 4 (Fast & Slow): 3 problems
- [ ] Start timing yourself (20-30 min per problem)
Week 5-6: Master It
- [ ] Pattern 5 (Reversal): 3 problems
- [ ] Pattern 6 (Monotonic Stack): 3 problems
- [ ] Try variations of each pattern
My Study Routine
Daily (1 hour):
- 30 min: New problem
- 20 min: Review yesterday's problem
- 10 min: Notes on what I learned
Weekend (2 hours):
- Review week's patterns
- Do contest problems
- Watch explanations for problems I couldn't solve
π Mistakes I Made (So You Don't Have To)
Mistake #1: Memorizing Solutions
What I did: Memorized code
What happened: Forgot in a week
What works: Understand the PATTERN
Mistake #2: Skipping Easy Problems
What I did: "Easy is boring"
What happened: Failed medium problems
What works: Master easy first, build confidence
Mistake #3: Not Drawing It Out
What I did: Solved in my head
What happened: Made silly mistakes
What works: Draw diagrams, trace examples
Mistake #4: Looking at Solutions Too Fast
What I did: Struggled 5 min β gave up
What happened: Learned nothing
What works: Struggle 30-45 min, look at HINTS first
Mistake #5: Not Reviewing
What I did: Solved once, never again
What happened: Forgot everything
What works: Review after 1 day, 1 week, before interview
π¬ Let's Connect!
We covered 6 essential patterns:
- β Prefix Sum
- β Two Pointers
- β Sliding Window
- β Fast & Slow Pointers
- β LinkedList Reversal
- β Monotonic Stack
Part 2 coming next week with patterns 7-15!
If this helped you:
- β€οΈ Like it
- π¬ Comment which pattern clicked for you
- π Share with friends
- π Bookmark for reference
Questions? Drop them below! I answer every single one! π
Made with β€οΈ, lots of debugging, and way too much coffee by Rajat
3rd Year CS Student | Still Learning | Probably Solving LeetCode Right Now
P.S. - Don't worry if patterns don't click immediately. Took me weeks to truly GET them. Keep practicing! πͺ
P.P.S. - Part 2 drops next week with Tree patterns, DFS/BFS, and DP. Follow so you don't miss it! π






Top comments (0)