LeetCode / NeetCode 150 — Pattern Recognition & Intuitive Approaches

Big-tech and frontier-lab coding-round preparation, organized by pattern recognition, not by individual problems. Built around the 18 NeetCode 150 categories. The goal is: read a problem, recognize the pattern in 30 seconds, write the right template.

The single biggest jump in coding-interview competence is moving from "let me try to solve this problem" to "this is a sliding-window problem; here's the template; let me adapt it." This chapter trains the second pattern.


Table of contents

  1. The 30-second triage — how to read any problem
  2. Arrays & Hashing
  3. Two Pointers
  4. Sliding Window
  5. Stack
  6. Binary Search
  7. Linked List
  8. Trees
  9. Tries
  10. Heap / Priority Queue
  11. Backtracking
  12. Graphs
  13. Advanced Graphs (MST, topo sort, Dijkstra, Bellman-Ford, Floyd-Warshall)
  14. 1-D Dynamic Programming
  15. 2-D Dynamic Programming
  16. Greedy
  17. Intervals
  18. Math & Geometry
  19. Bit Manipulation
  20. Problem-to-pattern transformation tricks
  21. Complexity reasoning cheatsheet
  22. Common interview traps
  23. The 5-step problem-solving protocol
  24. Drilling routine

1. The 30-second triage

When a problem lands, ask in this order:

(a) What's the input shape?

  • Single array → arrays/hashing, two pointers, sliding window, sorting, prefix sum.
  • Sorted array / monotonic property → binary search, two pointers.
  • 2D grid → BFS/DFS/DP/backtracking.
  • Tree / graph → recursion, BFS/DFS, topo, union-find.
  • String → hashing, two pointers, sliding window, DP, trie.
  • Number / bits → math, bit manipulation, DP.
  • Multiple inputs / intervals → sorting, sweep line, intervals.

(b) What's the output shape?

  • Yes/no, count, exists → often DP / hashing / sliding window.
  • Min/max value → DP, greedy, heap, binary search on answer.
  • All combinations / subsets / permutations → backtracking.
  • Single index / pair → two pointers, hashing.
  • Path / order → BFS, DFS, topo sort.

(c) Are there constraints that hint at complexity?

  • → exponential OK; backtracking, bitmask DP.
  • OK; 2-D DP, Floyd-Warshall.
  • OK; standard DP.
  • ; sort + binary search, heap.
  • ; hashing, sliding window, two pointers.
  • or math; binary search on answer, formulas.

(d) Are there words / structural cues?

  • "Subarray" / "contiguous" → sliding window or prefix sum.
  • "Subsequence" → DP.
  • "Sorted" → binary search or two pointers.
  • "Cycle" → fast-slow pointers (linked lists), DFS visited (graphs).
  • "Top-K / smallest-K / median" → heap.
  • "Connected" → union-find or DFS.
  • "Shortest path" → BFS (unweighted), Dijkstra (weighted), Bellman-Ford (negative), Floyd-Warshall (all-pairs).
  • "Balanced parentheses / next greater" → stack.
  • "All combinations" → backtracking.
  • "Number of ways" / "min cost to reach" → DP.

(e) Can you reduce to a simpler known problem?

  • Reverse the string and you have a known pattern.
  • Sort and the structure becomes obvious.
  • Build a graph from the input and BFS/DFS works.
  • Memoize the recursion and you have DP.

This 30-second triage is 80% of competence. State it explicitly during a real interview.


2. Arrays & Hashing

Recognition signals

  • "Find duplicates / pairs / sum / count of X."
  • "Group by some key."
  • or allowed.
  • Need fast lookup or counting.

Mental model

A hash map / set converts scans into lookups. Use it whenever you'd otherwise nest a loop.

Templates

Counting / frequency:

from collections import Counter
freq = Counter(arr)
for x, c in freq.items():
    ...

Index lookup (Two Sum-style):

seen = {}
for i, x in enumerate(arr):
    if target - x in seen:
        return [seen[target - x], i]
    seen[x] = i

Group by canonical form (anagrams):

groups = defaultdict(list)
for s in strs:
    key = tuple(sorted(s))    # or freq tuple
    groups[key].append(s)
return list(groups.values())

Representative problems

  • Two Sum. Hash index → .
  • Group Anagrams. Hash by sorted tuple → .
  • Top K Frequent Elements. Counter + heap of size . Or bucket sort → .
  • Product of Array Except Self. Prefix and suffix products → , no division.
  • Valid Sudoku. Three sets per (row, col, box), validate uniqueness.
  • Encode/Decode Strings. Length-prefix encoding.
  • Longest Consecutive Sequence. Hash set; for each n, only start counting from n if n-1 not in set → amortized.

Common traps

  • Forgetting that hashing strings has hidden cost where is string length.
  • Modifying a dict while iterating it.
  • Assuming hash maps preserve insertion order (Python 3.7+ does; Java's HashMap doesn't).
  • Using a list as a key (not hashable) — use a tuple.

3. Two Pointers

Recognition signals

  • "Sorted array."
  • "Find a pair / triplet / window with property X."
  • "Move toward each other" or "fast/slow."
  • Want to avoid an nested loop.

Mental model

Two indices into the array, advancing based on a comparison. Each pointer moves times total → linear time.

Templates

Opposing pointers (sorted array, target sum):

l, r = 0, len(arr) - 1
while l < r:
    s = arr[l] + arr[r]
    if s == target: return [l, r]
    if s < target:  l += 1
    else:           r -= 1

Same-direction (fast/slow, in-place modification):

slow = 0
for fast in range(len(arr)):
    if condition(arr[fast]):
        arr[slow] = arr[fast]
        slow += 1
return slow

Representative problems

  • Two Sum II (sorted). Opposing pointers.
  • 3Sum. Fix one element; two-pointer the rest. .
  • Container With Most Water. Opposing pointers; move the shorter side.
  • Trapping Rain Water. Two pointers + running max from each side.
  • Valid Palindrome. Skip non-alphanumeric, compare from outside in.
  • Remove Duplicates from Sorted Array. Slow/fast in place.

Common traps

  • Forgetting to skip duplicates in 3Sum (gives wrong answer with duplicate triplets).
  • Off-by-one in loop bounds.
  • Using two pointers on unsorted data (usually wrong).

4. Sliding Window

Recognition signals

  • "Subarray" / "contiguous substring."
  • "Longest / shortest / count of X with property Y."
  • target.
  • The property is monotonic in window size (extending the window only adds; shrinking only removes).

Mental model

Maintain a window . Expand to include new elements; when constraint violated, shrink . Each pointer moves → linear time.

Templates

Variable-size window (longest with property):

l = 0
state = init()
best = 0
for r, x in enumerate(arr):
    state = add(state, x)
    while not valid(state):
        state = remove(state, arr[l])
        l += 1
    best = max(best, r - l + 1)
return best

Fixed-size window (size ):

state = init()
for r in range(k):
    state = add(state, arr[r])
ans = state.value()
for r in range(k, len(arr)):
    state = add(state, arr[r])
    state = remove(state, arr[r - k])
    ans = better(ans, state.value())
return ans

Counting / "at most " trick:

def at_most_k(k):
    # number of subarrays with at-most-k of property X
    ...
# count exactly k = at_most_k(k) - at_most_k(k - 1)

Representative problems

  • Best Time to Buy and Sell Stock. Track running min, look for max diff. .
  • Longest Substring Without Repeating Characters. Hash window of seen chars; shrink on duplicate.
  • Longest Repeating Character Replacement. Track max char count in window; valid if window_size − max_count ≤ k.
  • Minimum Window Substring. Counter of needed chars; shrink while valid; track minimum.
  • Permutation in String. Fixed window of size = pattern length; compare counters.
  • Sliding Window Maximum. Monotonic deque (also see §5).

Common traps

  • Wrong shrink condition (when do you contract the window?).
  • Forgetting to update state on both add and remove.
  • Counting versus length tracking.
  • Treating subsequence problems as sliding-window (don't — those are DP).

5. Stack

Recognition signals

  • "Matching parentheses / brackets."
  • "Next greater element / nearest smaller."
  • "Evaluate expression / parse."
  • "Histogram / rectangle."
  • "Undo / backtrack."

Mental model

Last-in-first-out. Use when each item's processing depends on a recently-seen item that hasn't been resolved yet.

Templates

Matching:

pairs = {')': '(', ']': '[', '}': '{'}
stack = []
for c in s:
    if c in '([{':
        stack.append(c)
    else:
        if not stack or stack.pop() != pairs[c]: return False
return not stack

Monotonic stack (next greater):

res = [-1] * len(arr)
stack = []  # indices, decreasing values
for i, x in enumerate(arr):
    while stack and arr[stack[-1]] < x:
        res[stack.pop()] = x
    stack.append(i)
return res

Representative problems

  • Valid Parentheses. Standard stack matching.
  • Min Stack. Two stacks or store (val, current_min) tuples.
  • Evaluate Reverse Polish Notation. Stack of operands; pop two on operator.
  • Generate Parentheses. Backtracking — stack-of-thinking, not literal stack.
  • Daily Temperatures. Monotonic decreasing stack of indices.
  • Car Fleet. Sort by position desc; stack of times-to-target.
  • Largest Rectangle in Histogram. Monotonic stack; key insight: for each bar, find left+right boundaries via stack.

Common traps

  • Pushing/popping order wrong.
  • Forgetting to handle leftover stack items at the end (e.g., remaining bars in histogram).
  • Confusing monotonic increasing vs decreasing stacks.

Recognition signals

  • "Sorted" array.
  • target.
  • "Find / first / last / smallest X such that property holds."
  • A monotonic property — once it flips, it doesn't flip back.
  • Constraints up to on the answer space → "binary search on the answer."

Mental model

Each step halves the search space. Use whenever you can bisect a monotonic predicate.

Templates

Standard binary search:

l, r = 0, len(arr) - 1
while l <= r:
    m = (l + r) // 2
    if arr[m] == target: return m
    if arr[m] < target:  l = m + 1
    else:                r = m - 1
return -1

Find first / leftmost match (predicate-based):

l, r = 0, len(arr)
while l < r:
    m = (l + r) // 2
    if pred(arr[m]):  r = m
    else:             l = m + 1
return l   # first index where pred is True

Binary search on the answer:

def feasible(x): ...   # True iff answer x is feasible
l, r = lo, hi
while l < r:
    m = (l + r) // 2
    if feasible(m):  r = m
    else:            l = m + 1
return l

Representative problems

  • Binary Search. Standard.
  • Search a 2D Matrix. Treat 2D as flattened sorted 1D.
  • Koko Eating Bananas. Binary search on speed; predicate = "can finish in hours."
  • Find Minimum in Rotated Sorted Array. Binary search comparing to .
  • Search in Rotated Sorted Array. Binary search with rotation handling.
  • Time Based Key-Value Store. Per-key sorted timestamps; bisect.
  • Median of Two Sorted Arrays. Binary search on partition position. .

Common traps

  • Off-by-one between l <= r and l < r patterns. Pick one and stick with it.
  • Integer overflow on (l + r) // 2 in languages with fixed int (use l + (r - l) // 2).
  • Predicate not monotonic → binary search doesn't apply.
  • Forgetting which boundary to return.

7. Linked List

Recognition signals

  • Input is a linked list head pointer.
  • "Reverse / merge / cycle / kth-from-end."
  • space target → can't dump to array.

Mental model

Pointer manipulation. Always use a sentinel (dummy) node to simplify head edge cases.

Templates

Reverse:

prev, curr = None, head
while curr:
    nxt = curr.next
    curr.next = prev
    prev = curr
    curr = nxt
return prev

Cycle detection (Floyd / fast-slow):

slow = fast = head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
    if slow == fast: return True   # cycle
return False

Cycle entry (Floyd):

# After fast-slow meet, restart slow at head; advance both 1 step.
slow = head
while slow != fast:
    slow = slow.next
    fast = fast.next
return slow

Merge two sorted:

dummy = ListNode()
tail = dummy
while l1 and l2:
    if l1.val <= l2.val: tail.next = l1; l1 = l1.next
    else:                tail.next = l2; l2 = l2.next
    tail = tail.next
tail.next = l1 or l2
return dummy.next

Representative problems

  • Reverse Linked List. Standard.
  • Merge Two Sorted Lists. Standard with dummy.
  • Reorder List. Find middle (fast-slow); reverse second half; merge alternating.
  • Remove Nth From End. Two pointers; advance fast by n; then both.
  • Copy List with Random Pointer. Hash old→new, two passes; or interleave trick.
  • Add Two Numbers. Carry-based with dummy.
  • LRU Cache. Doubly linked list + hash map for get/put.
  • Merge K Sorted Lists. Heap of heads, .
  • Reverse Nodes in k-Group. Reverse , recurse.
  • Find the Duplicate Number. Floyd's cycle on the implicit "index → arr[index]" linked list.

Common traps

  • Losing the head — always use a dummy.
  • Forgetting to update prev.next and curr.next in the right order.
  • Off-by-one in "Nth from end" (n vs n+1 advance).
  • Cycle in input → infinite loop if not detecting.

8. Trees (Binary Trees, BST)

Recognition signals

  • Input is a tree root.
  • "DFS / BFS / level order / serialize."
  • "Path / depth / diameter / lowest common ancestor."
  • BST: "search / insert / delete / inorder is sorted."

Mental model

Trees naturally decompose: solve for left subtree, solve for right subtree, combine. Recursion is the dominant tool. BST adds the ordering invariant.

Templates

DFS (postorder, with return value):

def dfs(node):
    if not node: return base_case
    left  = dfs(node.left)
    right = dfs(node.right)
    return combine(left, right, node)

BFS (level order):

from collections import deque
q = deque([root])
levels = []
while q:
    level = []
    for _ in range(len(q)):
        node = q.popleft()
        level.append(node.val)
        if node.left:  q.append(node.left)
        if node.right: q.append(node.right)
    levels.append(level)
return levels

BST property exploitation:

def search(node, target):
    if not node or node.val == target: return node
    if target < node.val: return search(node.left,  target)
    else:                 return search(node.right, target)

Representative problems

  • Invert Binary Tree. Swap children recursively.
  • Maximum Depth. Recursion: 1 + max(depth(left), depth(right)).
  • Diameter of Binary Tree. DFS returning depth; track max(left+right) seen.
  • Balanced Binary Tree. DFS returning height or -1 (unbalanced sentinel).
  • Same Tree / Subtree. Recursive structural compare.
  • Lowest Common Ancestor.
    • BST: walk down; first node where p ≤ node ≤ q.
    • General tree: DFS; return non-null whenever both sides are non-null.
  • Binary Tree Right Side View. BFS, last node per level. Or DFS prioritizing right.
  • Construct Tree from Preorder + Inorder. Recursive; partition inorder around preorder root.
  • Validate BST. Recursion with (min, max) bounds.
  • Kth Smallest in BST. Inorder traversal; stop at .
  • Serialize/Deserialize Binary Tree. Preorder with null markers.

Common traps

  • BFS without level tracking when level matters.
  • Validate BST: comparing only with parent (wrong) vs with bounds (right).
  • Forgetting null base case → AttributeError.
  • LCA on BST without exploiting BST property → unnecessary .
  • Recursion depth limit on skewed trees in Python (sys.setrecursionlimit).

9. Tries

Recognition signals

  • Multiple string lookups / prefix queries.
  • "Word search / autocomplete / all words with prefix X."
  • Set of strings + repeated queries.

Mental model

A 26-ary tree (or 256-ary, or hash-based) where each path from root spells a string. Insert/lookup are where is word length.

Templates

class Trie:
    def __init__(self):
        self.root = {}
    def insert(self, word):
        node = self.root
        for c in word:
            node = node.setdefault(c, {})
        node['$'] = True
    def search(self, word):
        node = self.root
        for c in word:
            if c not in node: return False
            node = node[c]
        return '$' in node
    def starts_with(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node: return False
            node = node[c]
        return True

Representative problems

  • Implement Trie. Standard.
  • Design Add and Search Word. Trie + DFS for . wildcard.
  • Word Search II. Build trie of words; DFS the board; prune when no trie path matches.

Common traps

  • Using a class-per-node when a dict-of-dicts works fine.
  • Forgetting the end-of-word marker.
  • Heavy memory use (consider compressed/Patricia trie for large dictionaries).

10. Heap / Priority Queue

Recognition signals

  • "Top K / Kth largest / Kth smallest."
  • "Median of stream."
  • "Merge K sorted."
  • "Schedule / next event."
  • Want insert/extract.

Mental model

Heap maintains the smallest (min-heap) or largest (max-heap) at the root in ops. Two heaps balance to give a streaming median.

Templates

Top K largest (min-heap of size k):

import heapq
h = []
for x in arr:
    heapq.heappush(h, x)
    if len(h) > k:
        heapq.heappop(h)
return h           # k largest

Streaming median (two heaps):

class MedianFinder:
    def __init__(self):
        self.lo = []  # max-heap (negated)
        self.hi = []  # min-heap
    def add(self, x):
        heapq.heappush(self.lo, -x)
        heapq.heappush(self.hi, -heapq.heappop(self.lo))
        if len(self.hi) > len(self.lo):
            heapq.heappush(self.lo, -heapq.heappop(self.hi))
    def median(self):
        if len(self.lo) > len(self.hi): return -self.lo[0]
        return (-self.lo[0] + self.hi[0]) / 2

Merge K sorted lists:

h = [(node.val, i, node) for i, node in enumerate(lists) if node]
heapq.heapify(h)
dummy = ListNode(); tail = dummy
while h:
    val, i, node = heapq.heappop(h)
    tail.next = node; tail = node
    if node.next:
        heapq.heappush(h, (node.next.val, i, node.next))
return dummy.next

Representative problems

  • Kth Largest Element. Heap of size . Or quickselect → average.
  • Last Stone Weight. Max-heap; pop two; push diff.
  • K Closest Points to Origin. Heap of size .
  • Task Scheduler. Counter + heap by frequency; CPU cycles with cool-down.
  • Find Median from Data Stream. Two heaps.
  • Merge K Sorted Lists. Heap of heads.
  • Top K Frequent. Counter + heap, or bucket sort.
  • Design Twitter. Heap of latest tweets per follower.

Common traps

  • Python's heapq is min-heap only; use negation for max-heap.
  • Forgetting to handle ties when heap stores tuples.
  • Time complexity of heapify is , not .

11. Backtracking

Recognition signals

  • "All subsets / permutations / combinations."
  • "Find all paths / valid configurations."
  • Constraint satisfaction (Sudoku, N-Queens).
  • Small (≤ 20) — exponential is OK.

Mental model

DFS through the choice tree. At each node, try each option; recurse; undo (the "backtrack"). Prune branches that can't lead to a valid answer.

Templates

Standard backtrack:

def backtrack(state, choices):
    if is_complete(state):
        result.append(copy(state))
        return
    for c in choices:
        if not is_valid(state, c): continue
        apply(state, c)
        backtrack(state, next_choices(choices, c))
        undo(state, c)

Subsets:

def dfs(i, current):
    if i == len(arr):
        result.append(current[:])
        return
    # exclude arr[i]
    dfs(i + 1, current)
    # include arr[i]
    current.append(arr[i])
    dfs(i + 1, current)
    current.pop()

Permutations:

def dfs(remaining, current):
    if not remaining:
        result.append(current[:])
        return
    for i in range(len(remaining)):
        current.append(remaining[i])
        dfs(remaining[:i] + remaining[i+1:], current)
        current.pop()

Representative problems

  • Subsets / Subsets II. Include/exclude. For II, sort and skip duplicates at the same level.
  • Combination Sum / II / III. Backtrack with running sum and start index.
  • Permutations / II. Swap-in-place or used-array.
  • Word Search. DFS the grid with visited markers; backtrack on return.
  • Palindrome Partitioning. Try each prefix; if palindrome, recurse on suffix.
  • N-Queens. Place queens row by row with column / diagonal sets for check.
  • Letter Combinations of Phone Number. Backtrack by digit.
  • Restore IP Addresses. Try 1-3 chars, validate, recurse.
  • Sudoku Solver. Try 1-9 in each empty cell with row/col/box sets.

Common traps

  • Forgetting to undo state (the "backtrack" step).
  • Appending mutable state without copying — append current[:] not current.
  • Wrong start index in combinations vs permutations (combinations: i+1; permutations: include all).
  • Not handling duplicates when input has dups (sort + skip).

12. Graphs

Recognition signals

  • "Connected" / "shortest path" / "all paths" / "cycle."
  • Adjacency list / matrix or implicit grid.
  • "Number of islands / connected components."
  • BFS for unweighted shortest, DFS for connectivity.

Mental model

A graph is nodes + edges. Visit each node once with a visited set; recurse / queue neighbors; combine results.

Templates

DFS (recursive):

visited = set()
def dfs(u):
    visited.add(u)
    for v in graph[u]:
        if v not in visited: dfs(v)

BFS (queue):

from collections import deque
q = deque([(start, 0)])
visited = {start}
while q:
    u, d = q.popleft()
    if u == target: return d
    for v in graph[u]:
        if v not in visited:
            visited.add(v); q.append((v, d + 1))
return -1

Grid traversal:

DIRS = [(-1, 0), (1, 0), (0, -1), (0, 1)]
def dfs(r, c):
    if not (0 <= r < R and 0 <= c < C): return
    if grid[r][c] != target: return
    grid[r][c] = '#'   # mark visited in-place (or use set)
    for dr, dc in DIRS:
        dfs(r + dr, c + dc)

Union-Find (DSU):

parent = list(range(n))
rank = [0] * n
def find(x):
    while parent[x] != x:
        parent[x] = parent[parent[x]]   # path compression
        x = parent[x]
    return x
def union(a, b):
    ra, rb = find(a), find(b)
    if ra == rb: return False
    if rank[ra] < rank[rb]: ra, rb = rb, ra
    parent[rb] = ra
    if rank[ra] == rank[rb]: rank[ra] += 1
    return True

Representative problems

  • Number of Islands. DFS/BFS each '1' cell, mark visited.
  • Clone Graph. DFS/BFS with old→new map.
  • Max Area of Island. DFS with returned area.
  • Pacific Atlantic Water Flow. BFS from each ocean's edge cells; intersection.
  • Surrounded Regions. DFS from edge 'O' to mark "safe"; flip rest.
  • Rotting Oranges. Multi-source BFS; track minutes.
  • Walls and Gates. Multi-source BFS from gates.
  • Course Schedule. Topological sort (next section).
  • Redundant Connection. Union-Find; the edge that creates a cycle.
  • Number of Connected Components. DFS-count or Union-Find.
  • Graph Valid Tree. nodes, edges, no cycle, connected — Union-Find or DFS.
  • Word Ladder. BFS on word graph, transform one letter at a time.

Common traps

  • Forgetting the visited set → infinite recursion.
  • BFS without level tracking when distance matters.
  • Mutating the input grid without restoring (some interviewers care).
  • Stack overflow on huge DFS in Python — convert to iterative or setrecursionlimit.

13. Advanced Graphs

Algorithms that build on the basics.

13.1 Topological sort

Signal: "Order tasks subject to prerequisites." DAG. Course Schedule, Build Order.

Kahn's algorithm (BFS):

indeg = [0] * n
for u, v in edges:
    indeg[v] += 1
q = deque([i for i in range(n) if indeg[i] == 0])
order = []
while q:
    u = q.popleft()
    order.append(u)
    for v in graph[u]:
        indeg[v] -= 1
        if indeg[v] == 0: q.append(v)
return order if len(order) == n else []   # cycle if not all included

DFS-based: post-order DFS; reverse the result. Detect cycle via "in-stack" marker.

13.2 Dijkstra (single-source shortest path, non-negative weights)

import heapq
dist = [float('inf')] * n; dist[src] = 0
h = [(0, src)]
while h:
    d, u = heapq.heappop(h)
    if d > dist[u]: continue
    for v, w in graph[u]:
        if dist[u] + w < dist[v]:
            dist[v] = dist[u] + w
            heapq.heappush(h, (dist[v], v))
return dist

.

13.3 Bellman-Ford (handles negative weights)

dist = [float('inf')] * n; dist[src] = 0
for _ in range(n - 1):
    for u, v, w in edges:
        if dist[u] + w < dist[v]:
            dist[v] = dist[u] + w
# Detect negative cycle: one more pass; if any update, there's a cycle.

. Use when negative weights exist (Cheapest Flights Within K Stops).

13.4 Floyd-Warshall (all-pairs)

for k in range(n):
    for i in range(n):
        for j in range(n):
            if dist[i][k] + dist[k][j] < dist[i][j]:
                dist[i][j] = dist[i][k] + dist[k][j]

. Use when small (~500) and you need all-pairs.

13.5 Minimum Spanning Tree (MST)

Kruskal's — sort edges; Union-Find to add if not creating cycle.

edges.sort(key=lambda e: e[2])
uf = UnionFind(n)
mst_weight = 0
for u, v, w in edges:
    if uf.union(u, v):
        mst_weight += w

Prim's — Dijkstra-like, but distance is from "any tree node," not from source.

13.6 Representative problems

  • Course Schedule / II. Topological sort.
  • Network Delay Time. Dijkstra.
  • Cheapest Flights Within K Stops. Bellman-Ford with at most K relaxations.
  • Min Cost to Connect All Points. MST (Kruskal or Prim).
  • Swim in Rising Water. Dijkstra with max-along-path metric, or binary search + BFS.
  • Reconstruct Itinerary. Hierholzer's (Eulerian path) or DFS with sorted destinations.
  • Alien Dictionary. Build precedence graph; topological sort.
  • Foreign Dictionary / Find Order. Same family.

13.7 Common traps

  • Dijkstra with negative weights → wrong answer.
  • Topological sort on cyclic graph → not all nodes appear → detect.
  • Using BFS instead of Dijkstra when edges have non-uniform weights.
  • Forgetting if d > dist[u]: continue in Dijkstra → unnecessary relaxations.

14. 1-D Dynamic Programming

Recognition signals

  • "Number of ways," "min/max value," subsequence.
  • Subproblems overlap; optimal substructure.
  • Brute force is exponential but state space is polynomial.
  • State can be expressed by an index i (sometimes plus a small flag).

Mental model

Define dp[i] = answer to subproblem ending at / using index . Express dp[i] in terms of earlier dp[j]. Base case + transition + final answer.

Templates

Top-down memoization:

from functools import lru_cache
@lru_cache(None)
def f(i):
    if base(i): return ...
    return combine(f(i-1), f(i-2), ...)

Bottom-up tabulation:

dp = [0] * (n + 1)
dp[0] = base
for i in range(1, n + 1):
    dp[i] = combine(dp[i-1], dp[i-2], ...)
return dp[n]

Space-optimized (only last few values):

prev2, prev1 = ..., ...
for i in range(2, n + 1):
    curr  = combine(prev1, prev2, ...)
    prev2 = prev1
    prev1 = curr
return prev1

Representative problems

  • Climbing Stairs / Fib. .
  • Min Cost Climbing Stairs. Like Fib with cost.
  • House Robber. .
  • House Robber II. Two passes (skip first or skip last).
  • Longest Palindromic Substring. Expand around centers, . Or Manacher's .
  • Palindromic Substrings. Expand around centers; count.
  • Decode Ways. = ways to decode prefix of length .
  • Coin Change. over coins. Unbounded knapsack.
  • Maximum Product Subarray. Track both max and min ending at (negative flips).
  • Word Break. = can break prefix of length .
  • Longest Increasing Subsequence. DP, or with patience sorting.
  • Partition Equal Subset Sum. Subset sum to total/2; 0/1 knapsack.

Common traps

  • Confusing subarray (contiguous) with subsequence (non-contiguous).
  • Off-by-one in indexing (e.g., dp[0] vs dp[1] as base case).
  • Forgetting to initialize the base case.
  • Using space when suffices.
  • Coin Change unbounded vs 0/1 — different recurrence.

15. 2-D Dynamic Programming

Recognition signals

  • Two indices in the state (string i, string j; row, col; range ).
  • "Edit distance / longest common subsequence / interleaving."
  • Grid path counting / min cost.
  • Range queries.

Mental model

dp[i][j] for two-string or grid problems. Often the recurrence is "match or don't match" or "go right or down."

Templates

LCS:

dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
    for j in range(1, n + 1):
        if a[i-1] == b[j-1]:
            dp[i][j] = dp[i-1][j-1] + 1
        else:
            dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]

Grid path:

dp[0][0] = grid[0][0]
for i in range(m):
    for j in range(n):
        if i == 0 and j == 0: continue
        a = dp[i-1][j] if i > 0 else inf
        b = dp[i][j-1] if j > 0 else inf
        dp[i][j] = grid[i][j] + min(a, b)
return dp[m-1][n-1]

Range DP:

for L in range(2, n + 1):                   # window length
    for i in range(n - L + 1):
        j = i + L - 1
        dp[i][j] = combine_over(dp[i][k], dp[k+1][j] for k in range(i, j))

Representative problems

  • Unique Paths / II. Grid path count; II has obstacles.
  • Longest Common Subsequence. Standard LCS.
  • Edit Distance. Insert/delete/replace recurrence.
  • Distinct Subsequences. Count of t in s.
  • Interleaving String. Boolean DP.
  • Best Time to Buy/Sell with Cooldown / Fee / K Transactions. State = (i, holding, last action).
  • Target Sum. Reduces to subset sum count.
  • Burst Balloons. Range DP.
  • Regular Expression Matching / Wildcard Matching. for prefix matches.
  • Longest Increasing Path in a Matrix. DFS with memoization.
  • Maximal Rectangle. Histogram per row + Largest Rectangle in Histogram.

Common traps

  • Forgetting to handle the "first row" and "first column" of a grid DP.
  • Range DP loop order (must process smaller ranges first).
  • Confusing "subsequence" with "substring" — the recurrence differs.
  • LCS variants: longest common substring is different (resets on mismatch).

16. Greedy

Recognition signals

  • "Minimum / maximum X with simple rule."
  • A local optimal choice leads to global optimal.
  • Often involves sorting first.
  • Can prove correctness via exchange argument.

Mental model

At each step, pick the locally best option without considering future. Hard to know when greedy works without proof — common in interval / scheduling problems.

Templates

Sort + sweep:

arr.sort(key=...)
result = init
for x in arr:
    if condition(x, result):
        result = update(result, x)
return result

Representative problems

  • Maximum Subarray (Kadane's). Track current sum; reset to 0 if negative. .
  • Jump Game / II. Greedy: track the farthest reachable.
  • Gas Station. Skip to next station after a deficit; one pass.
  • Hand of Straights. Sort + use Counter; greedily form groups starting from min.
  • Merge Triplets to Form Target. Filter triplets that don't exceed target; check max in each position.
  • Partition Labels. Compute last index per char; sweep.
  • Valid Parenthesis String. Track range of possible open counts.

Common traps

  • Assuming greedy works without proof — it often doesn't on problems that look greedy. (Coin change with arbitrary denominations is not greedy; with US coins, it is.)
  • Wrong sort key.
  • Edge cases at boundaries.

17. Intervals

Recognition signals

  • Input is a list of intervals [start, end].
  • "Merge overlapping / insert / count overlapping / minimum rooms."

Mental model

Sort intervals (usually by start). Sweep. Track running state (current end, active count, etc.).

Templates

Merge overlapping:

intervals.sort()
result = [intervals[0]]
for s, e in intervals[1:]:
    if s <= result[-1][1]:
        result[-1][1] = max(result[-1][1], e)
    else:
        result.append([s, e])
return result

Sweep line / minimum rooms:

events = []
for s, e in intervals:
    events.append((s, 1))   # start
    events.append((e, -1))  # end
events.sort()
active = max_active = 0
for _, delta in events:
    active += delta
    max_active = max(max_active, active)
return max_active

Representative problems

  • Insert Interval. Three phases: before, overlap, after.
  • Merge Intervals. Sort + sweep.
  • Non-overlapping Intervals. Sort by end; greedy keep earliest-ending.
  • Meeting Rooms / II. Sort + sweep / heap.
  • Minimum Interval to Include Each Query. Sort intervals + queries; heap of active intervals.

Common traps

  • Sort by start vs by end matters.
  • "Touch" vs "overlap" (<= vs <).
  • Empty input.

18. Math & Geometry

Recognition signals

  • Number theory, modular arithmetic, GCD/LCM.
  • Geometry: rotation, reflection.
  • "Without using the obvious O(n) data structure."

Mental model

Often there's a closed-form or a clever invariant.

Representative problems

  • Rotate Image. Transpose + reverse rows.
  • Spiral Matrix. Layered traversal with shrinking bounds.
  • Set Matrix Zeroes. Use first row/col as markers; extra.
  • Happy Number. Floyd cycle detection on the digit-sum-of-squares function.
  • Plus One. Carry propagation.
  • Pow(x, n). Fast exponentiation in .
  • Multiply Strings. Schoolbook with carry, build result in reverse.
  • Detect Squares. Hash points; brute force pairs.

Common traps

  • Off-by-one in spiral.
  • Negative exponent in Pow → 1/Pow(x, -n) and handle int min.
  • Integer overflow in languages with fixed int.

19. Bit Manipulation

Recognition signals

  • "Without using extra space."
  • "Single number / pair where everyone else appears twice."
  • Constraints involving powers of 2 or binary representations.

Mental model

XOR has cancellation. AND extracts. OR sets. Shift moves. Many problems reduce to clever combinations.

Useful identities

  • x ^ x = 0, x ^ 0 = x → XOR cancels.
  • x & (x - 1) clears lowest set bit.
  • x & -x isolates lowest set bit.
  • Popcount: Brian Kernighan's loop or bin(x).count('1').
  • Toggle bit : x ^ (1 << k).
  • Set bit : x | (1 << k).
  • Clear bit : x & ~(1 << k).
  • Test bit : (x >> k) & 1.

Representative problems

  • Single Number. XOR all → leftover is the unique. time, space.
  • Number of 1 Bits. Brian Kernighan loop.
  • Counting Bits (0..n). .
  • Reverse Bits. Bit-by-bit reverse, or divide-and-conquer in halves.
  • Missing Number. XOR 0..n with arr.
  • Sum of Two Integers without +. Loop with AND-shift carry, XOR sum.

Common traps

  • Negative numbers in two's complement.
  • Bit-shift on signed integers.
  • Off-by-one on bit indexing.

20. Problem-to-pattern transformation tricks

These are the "ah-ha" moves that turn a hard problem into a known pattern.

  • Sort first. Often turns chaos into an ordered sweep / two-pointer / greedy.
  • Build a graph. Many problems become BFS/DFS once you express dependencies.
  • Reverse direction. Sometimes processing from right or end-to-start exposes structure.
  • Prefix sums. Range sum queries → prefix array, then differences.
  • Difference array. Range update → mark deltas at endpoints; sweep.
  • Bucketize. Continuous values → buckets → counting / sliding window.
  • Coordinate compression. Replace original values by their rank when only ordering matters.
  • Two passes. Left-to-right + right-to-left and combine. Trapping Rain Water, Product Except Self.
  • Inversion / negation. Find the smallest such that NOT property holds, etc.
  • Binary search on the answer. Numerical answer space + monotonic feasibility.
  • Hash by canonical form. Anagrams → sorted tuple. Isomorphic strings → first-occurrence index.
  • Count instead of construct. Often you just need a number, not the actual object.
  • Memoize the recursion. Brute-force recursion + memoization = DP.
  • Floyd cycle on implicit graph. Find the Duplicate Number; Happy Number.
  • Treat 2D as 1D. Search a 2D Matrix → flatten via row*ncols + col.
  • Split into halves. Median of Two Sorted Arrays. Quickselect partitioning.

21. Complexity reasoning cheatsheet

  • — binary search; balanced BST ops; heap push/pop.
  • — primality; Mo's algorithm.
  • — single pass; hashing; counting; sliding window.
  • — sorting; heap on elements; balanced BST.
  • — nested loops; basic DP on pairs; LCS.
  • — Floyd-Warshall; certain range DP.
  • — bitmask DP over subsets.
  • — brute-force subsets; certain backtracking.
  • — permutations.

If , you need or better. If , might work. If , exponential is fine.


22. Common interview traps

  • Solving silently. Always think aloud. Interviewer is rating your reasoning, not your final code.
  • Diving into code too fast. Spend 2-5 minutes restating, examples, brute force, optimal idea, complexity, then code.
  • Wrong data structure. Not using a hash map → when is expected.
  • Off-by-one. Especially in binary search and sliding window.
  • Uninitialized state. DP base case; visited set.
  • Mutating input without permission. Some interviewers care.
  • Edge cases ignored. Empty input, single element, all duplicates, negatives.
  • No complexity analysis. Always state time and space at the end.
  • No testing. Trace through your code with a small example before declaring done.
  • Premature optimization. Brute force first if you're stuck.
  • Stuck silently. When stuck, narrate: "If I sort first, then..." or "What if I tried two pointers here?"
  • Not asking clarifying questions. Constraints, duplicates, negatives, output format.
  • Memorizing without understanding. Interviewer asks a variant; you can't adapt.
  • Recursion depth. Python defaults to 1000; for huge inputs, iterate or sys.setrecursionlimit.
  • Integer overflow. Not in Python, but in Java/C++ — multiply two ints often overflows.

23. The 5-step problem-solving protocol

Use this verbatim in every interview.

Step 1: Understand. Read aloud. Ask about constraints, duplicates, negatives, edge cases, output format.

Step 2: Examples. Walk through the given examples + 1-2 you make up (edge cases).

Step 3: Brute force. State the brute-force approach and complexity. ("I could try every pair, .")

Step 4: Optimize. Identify the pattern. ("This looks like sliding window because...") State the optimal approach and complexity.

Step 5: Code. Write clean code. State invariants. Trace through an example. State final time/space complexity.

If stuck at step 4, return to step 3 and code the brute force; many interviews accept it.


24. Drilling routine

Two months out from a coding round:

  • Week 1-2: Arrays, hashing, two pointers, sliding window. Solve 5 per pattern.
  • Week 3-4: Stack, binary search, linked list. 5 per pattern.
  • Week 5-6: Trees, tries, heap. 5 per pattern.
  • Week 7: Backtracking, graphs, advanced graphs. 5 per pattern.
  • Week 8: 1D and 2D DP. 5+ per pattern.
  • Week 9: Greedy, intervals, math, bits. 3 per pattern.
  • Last week: Mock interviews, mixed problems on shuffle, one set per day.

Daily routine:

  1. Pick a problem.
  2. Try 30 minutes solo (use the 5-step protocol).
  3. Check editorial / discussion.
  4. If new pattern, take 10 minutes to add to your notes.
  5. Re-attempt next day from scratch.

How to use this chapter

  1. Read once, top to bottom. Note which patterns feel weak.
  2. For weak patterns, drill 5+ problems each.
  3. Re-read §1 (triage), §20 (transformations), §22 (traps), §23 (protocol) every Sunday.
  4. Pair with INTERVIEW_GRILL.md — pattern-recognition active recall.
  5. Practice on NeetCode 150 / Blind 75 / Grind 75 — the curated lists.

The single sentence to remember: for any problem, do the 30-second triage by input shape, output shape, constraints, and structural cues; then match to one of 18 patterns; then deploy the template.