Lesson 8 of 8 · 52 min
DP, backtracking, and the capstone mock
Core DP and backtracking templates, then a full rubric mock on LRU cache, token-bucket rate limiter, and streaming median — production-flavored close to the track.
DP is caching with a story; backtracking is search with undo
Key idea
DP core: robber and coin change
1HOUSE ROBBER — 1D DP state is max through i23def rob(nums: list[int]) -> int:4 prev2 = prev1 = 0 # dp[i-2], dp[i-1]5 for x in nums:6 prev2, prev1 = prev1, max(prev1, prev2 + x)7 return prev189# Recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i])10# Coin change (min coins): dp[0]=0; dp[a]=min over coin c of dp[a-c]+1; inf if impossible.1COIN CHANGE — unbounded knapsack style23def coin_change(coins: list[int], amount: int) -> int:4 INF = amount + 15 dp = [INF] * (amount + 1)6 dp[0] = 07 for a in range(1, amount + 1):8 for c in coins:9 if c <= a:10 dp[a] = min(dp[a], dp[a - c] + 1)11 return dp[amount] if dp[amount] < INF else -11213# O(amount * |coins|). State sentence: min coins to make exact sum a.Backtracking template
1SUBSETS — include/exclude backtracking23def subsets(nums: list[int]) -> list[list[int]]:4 ans: list[list[int]] = []5 path: list[int] = []67 def bt(i: int) -> None:8 if i == len(nums):9 ans.append(path.copy())10 return11 # exclude12 bt(i + 1)13 # include14 path.append(nums[i])15 bt(i + 1)16 path.pop()1718 bt(0)19 return ans2021# Word search: mark board[r][c] visited, recurse 4 dirs, unmark — same undo discipline.Common mistake
“DP means fill a 2D table bottom-up always.”
Common mistake
“Backtracking and DFS are unrelated to interviews about DP.”
LCS / LIS and state sentences
1LCS LENGTH — 2D DP with a one-sentence state23def lcs(a: str, b: str) -> int:4 n, m = len(a), len(b)5 dp = [[0] * (m + 1) for _ in range(n + 1)]6 for i in range(1, n + 1):7 for j in range(1, m + 1):8 if a[i - 1] == b[j - 1]:9 dp[i][j] = dp[i - 1][j - 1] + 110 else:11 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])12 return dp[n][m]1314# State: dp[i][j] = LCS length of a[:i], b[:j]. Say it before loops.Capstone mock — production structures under the rubric
1LRU CACHE — map + doubly linked list (interview skeleton)23class Node:4 def __init__(self, k=0, v=0):5 self.k, self.v = k, v6 self.prev = self.next = None78class LRUCache:9 def __init__(self, capacity: int):10 self.cap = capacity11 self.map: dict[int, Node] = {}12 self.head, self.tail = Node(), Node() # sentinels13 self.head.next, self.tail.prev = self.tail, self.head1415 def _remove(self, n: Node) -> None:16 n.prev.next, n.next.prev = n.next, n.prev1718 def _add_front(self, n: Node) -> None:19 n.next, n.prev = self.head.next, self.head20 self.head.next.prev, self.head.next = n, n2122 def get(self, key: int) -> int:23 if key not in self.map:24 return -125 n = self.map[key]26 self._remove(n); self._add_front(n)27 return n.v2829 def put(self, key: int, value: int) -> None:30 if key in self.map:31 self._remove(self.map[key])32 n = Node(key, value)33 self.map[key] = n34 self._add_front(n)35 if len(self.map) > self.cap:36 lru = self.tail.prev37 self._remove(lru)38 del self.map[lru.k]1TOKEN BUCKET — O(1) allow() with fractional refill23import time45class TokenBucket:6 def __init__(self, rate: float, burst: float):7 self.rate = rate # tokens per second8 self.burst = burst # max tokens9 self.tokens = burst10 self.updated = time.monotonic()1112 def allow(self) -> bool:13 now = time.monotonic()14 elapsed = now - self.updated15 self.updated = now16 self.tokens = min(self.burst, self.tokens + elapsed * self.rate)17 if self.tokens >= 1.0:18 self.tokens -= 1.019 return True20 return False2122# Clarify: multi-thread needs a lock; distributed needs Redis — say as follow-up.Key idea
What this track deliberately skipped
Caching is DP in production clothing. Rate limits and LRUs are the “build from scratch” rounds AI labs actually run. Narrate them like incidents, not like trivia.
Interview prep
- 01“How do you know it’s DP?” → overlapping subproblems + optimal substructure; name state.
- 02“Coin change recurrence?” → dp[a] = min(dp[a−c]+1); dp[0]=0.
- 03“Backtracking template?” → choose → recurse → undo; prune early.
- 04“LRU O(1)?” → hashmap + doubly linked list (or OrderedDict with move_to_end).
- 05“Token bucket vs fixed window?” → burst-friendly continuous refill vs rigid window counters.
- 06“Streaming median?” → two heaps; rebalance; O(log n) add.
- 07“DP vs backtracking for word break?” → DP boolean reachability vs listing all segmentations.
- 08“Self-grade?” → four axes 1–4; any 1 is fail; redo within 48h.
- 09"LRU two structures?" -> hashmap + doubly linked list for O(1) reorder/evict.
- 10"Token vs leaky?" -> token allows bursts to C; leaky smooths at constant drain.
- 11"Median heaps invariant?" -> lo max-heap, hi min-heap; sizes balanced; all lo <= all hi.
- 12"Distributed rate limit?" -> Redis Lua atomic refill+consume across instances.
- 13"DP vs backtracking word break?" -> boolean/count = DP; list segmentations = backtracking.
- 14"Self-grade capstone?" -> four axes 1-4 after timed mock; redo any <=2 within 48h.
LRU Cache — NeetCodeNeetCodeCapstone full teach: LRU + token bucket + streaming median
1STREAMING MEDIAN — two heaps full (LC 295)23import heapq45class MedianFinder:6 def __init__(self):7 self.lo: list[int] = [] # max-heap via negation8 self.hi: list[int] = [] # min-heap910 def add_num(self, num: int) -> None:11 if not self.lo or num <= -self.lo[0]:12 heapq.heappush(self.lo, -num)13 else:14 heapq.heappush(self.hi, num)15 if len(self.lo) < len(self.hi):16 heapq.heappush(self.lo, -heapq.heappop(self.hi))17 elif len(self.lo) - len(self.hi) > 1:18 heapq.heappush(self.hi, -heapq.heappop(self.lo))1920 def find_median(self) -> float:21 if len(self.lo) > len(self.hi):22 return float(-self.lo[0])23 return (-self.lo[0] + self.hi[0]) / 2.02425# Dry-run 1,2,3 -> 2; +4 -> 2.5. Rebalance after every insert.1TOKEN BUCKET — lock + fractional refill (distributed note)23import time, threading45class TokenBucket:6 def __init__(self, rate: float, burst: float):7 self.rate, self.burst = rate, burst8 self.tokens = burst9 self.updated = time.monotonic()10 self._lock = threading.Lock()1112 def allow(self, cost: float = 1.0) -> bool:13 with self._lock:14 now = time.monotonic()15 elapsed = now - self.updated16 self.updated = now17 self.tokens = min(self.burst, self.tokens + elapsed * self.rate)18 if self.tokens >= cost:19 self.tokens -= cost20 return True21 return False2223# Multi-instance: Redis Lua EVAL atomic refill+consume. Metrics: reject rate, burst usage.Common mistake
"OrderedDict alone is enough explanation for LRU."
Common mistake
"Token bucket and leaky bucket are the same."
Key idea
Find Median from Data Stream — NeetCodeNeetCodearticleRate limiting algorithms — AlgoMasterAlgoMasterCheckpoint
You need min number of coins to make amount (unlimited coins). Approach?
Checkpoint
Implement LRU: you use a list and .remove(key) each access. Interviewer asks complexity?
Checkpoint
Token bucket allow() under multi-threaded web server. What do you mention unprompted?
Checkpoint
Capstone self-grade: perfect code, zero narration, no tests. Axes?
Checkpoint
Word search on board: after exploring one path that failed, what must you do?
Could you run a 45-minute mock on LRU + one of (token bucket, streaming median), narrate the four axes, and honestly self-score?
Takeaways
- DP: name state + recurrence + base; memo or bottom-up.
- Backtracking: choose → recurse → undo; prune.
- Capstone: LRU (map+DLL), token bucket O(1) refill, two-heap median.
- Self-grade all four axes; silence fails even with correct code.
- Pattern-first track complete — keep a failure log of dry-run bugs.
Track complete. Schedule 2–3 timed mocks/week; review failures by pattern, not by random problem count.
Sources
Free to read · better with Enzo
Learn it with Enzo
Save your progress, answer the checkpoints, and let Enzo quiz you on what you just read.