Pattern #65
Memoization
Eliminate exponential recursive work by caching evaluated subproblems. Master Top-Down DP, complete multi-variable state keys, and demand-driven execution.
Must Solve
20 core questions β solve these first.
- 1.Fibonacci NumberBasic Top-Down memoization: memo[n] caches fib(n-1) + fib(n-2)easy
- 2.Climbing Stairs1D Memoization: memo[i] = solve(i+1) + solve(i+2)easy
- 3.House RobberTake vs Skip: memo[i] = max(solve(i+1), nums[i] + solve(i+2))medium
- 4.Coin ChangeUnbounded memo: memo[amount] = 1 + min(solve(amount - coin))medium
- 5.Word BreakSuffix Memoization: memo[start] = boolean canSegment(s[start:])medium
- 6.Decode WaysString Memoization: memo[i] = solve(i+1) + solve(i+2)medium
- 7.Unique PathsGrid Memoization: memo[r][c] = solve(r+1,c) + solve(r,c+1)medium
- 8.Minimum Path SumGrid Memoization: memo[r][c] = grid[r][c] + min(down, right)medium
- 9.Longest Common Subsequence2D Memo key (i, j): match -> 1 + solve(i+1, j+1), else max(down, right)medium
- 10.Edit Distance2D Memo key (i, j): 1 + min(insert, delete, replace)medium
- 11.Partition Equal Subset Sum2D State memo[i][target]: Take vs Skip item imedium
- 12.Target SumState (index, currentSum) converted to positive offset memomedium
- 13.Longest Increasing SubsequenceMemo key (index, prevIndex) or 1D suffix memo[i]medium
- 14.Combination Sum IVUnbounded memo[target] = sum(solve(target - coin))medium
- 15.Perfect SquaresUnbounded memo[n] = 1 + min(solve(n - s*s))medium
- 16.Interleaving String2D Memo key (i, j): checks prefix match of s1[i:] and s2[j:] with s3medium
- 17.Longest Palindromic SubsequenceSubstring Memo key (i, j): match -> 2 + solve(i+1, j-1)medium
- 18.House Robber IIITree Memo: postorder DFS with memoization or (rob, skip) tuplemedium
- 19.Longest Increasing Path in a MatrixDFS + Memo: memo[r][c] = 1 + max(dfs(neighbors))hard
- 20.Different Ways to Add ParenthesesExpression Divide & Conquer + string memoizationmedium
Also Important
10 advanced memoization variations worth practicing.
- 21.Burst BalloonsInterval Memo key (left, right): try last balloon k burst in rangehard
- 22.Stone GameGame Theory Memo key (i, j): max score difference between playersmedium
- 23.Regular Expression Matching2D Memo key (i, j) for text i and pattern j with . and *hard
- 24.Distinct SubsequencesString Memo key (i, j): count ways to match target subsequencehard
- 25.Dungeon GameReverse Grid Memo: memo[r][c] = min health required at (r, c)hard
- 26.Scramble String3D Memo or string key (s1, s2): recursive split comparisonhard
- 27.Palindrome Partitioning variantsMemo key (start): min cuts or valid partition listsmedium
- 28.DP on TreesDFS + Memoization caching subtree state queriesmedium
- 29.DFS + Memoization on DAGTopological order implicit via DFS memoization on DAG nodesmedium
- 30.Bitmask DP with MemoizationState (node, mask): memoization over subset bitmaskshard
How to Think
Convert slow top-down recursion into optimal DP by checking the memo cache before exploring subproblems.
- Recursive solution works but is too slow?Memoization
- Same function arguments appear again?Cache that state
- Need Top-Down DP?Recursion + Memo
- State has multiple variables?Struct / Matrix Key
- Need to skip invalid / duplicate states?Lookup ok in memo
Go Quick Reference β Memoization
Compilable Go code demonstrating Top-Down memoization with map lookups (`val, ok`) and multi-parameter struct keys.
package main
import "fmt"
// Multi-variable state struct (comparable type for Go map keys)
type StateKey struct {
Index int
Capacity int
}
func knapsackMemo(weights, values []int, index, capacity int, memo map[StateKey]int64) int64 {
// 1. Base Case
if index < 0 || capacity <= 0 {
return 0
}
// 2. Cache Check (Lookup ok in memo map)
key := StateKey{Index: index, Capacity: capacity}
if val, ok := memo[key]; ok {
return val // CACHE HIT!
}
// 3. Solve Smaller States
skip := knapsackMemo(weights, values, index-1, capacity, memo)
var take int64 = 0
if weights[index] <= capacity {
take = int64(values[index]) + knapsackMemo(weights, values, index-1, capacity-weights[index], memo)
}
best := skip
if take > skip {
best = take
}
// 4. Store in Cache & Return
memo[key] = best
return best
}
func main() {
weights := []int{2, 3, 4, 5}
values := []int{3, 4, 5, 6}
memo := make(map[StateKey]int64)
maxVal := knapsackMemo(weights, values, len(weights)-1, 5, memo)
fmt.Println("Max Knapsack Value:", maxVal) // Output: 7
}π Golden Structure: Base Case β Cache Check β Solve β Store β Return.
Core Memoization Pattern
The Golden Sequence
Every Top-Down memoized function follows 5 distinct steps:
1. Base Cases: Return trivial boundary answers.
2. Memo Hit: Check if state exists in cache; return saved answer.
3. Recursion: Solve smaller subproblem choices.
4. Store: Save result in cache before returning.
5. Return: Return computed value.
Complete Memo State Key Rule
Your memo key MUST include every parameter that changes future decisions.
β’ memo[index] β INCORRECT if capacity or stock state changes!
β’ memo[index][capacity] β CORRECT!
Base Case β Cache Check β Solve β Store β Return
Memo Key = Complete State (All changing variables)
Visited Array (Boolean) β Memoized Result (Computed Value)
Time Complexity = Unique States Γ Work per State
Space Complexity = Memo Storage + Recursion Stack DepthMemoization Variants & Applications
DFS + Memoization on Grids & DAGs
β’ Grid DFS: dfs(r, c) calculates longest increasing path or reachable target. Multiple paths hitting (r, c) trigger cache hits.
β’ DAG DFS: Nodes with multiple parents (e.g. A β C, B β C) evaluate C once and cache memo[C].
Bitmask & Tree Memoization
β’ Bitmask DP: For small N, encode visited subsets into integer bitmasks. State: (node, mask).
β’ Tree DP: Postorder recursion caches subtree queries to prevent repeated tree traversals.
Memoization vs Visited Array
β’ Visited Array (visited[node] = true): Tracks boolean reachability to prevent cycle loops.
β’ Memo Cache (memo[node] = result): Stores the computed answer value for subproblems starting at that node.
Crucial: Always cache BOTH successful and failed states (e.g. false or -1) so failed searches are not re-explored.
Common Interview Mistakes
- β’ Memoizing with an incomplete state key (e.g. storing memo[index] when capacity or stock status changes future choices).
- β’ Forgetting to store the result in the memo cache before returning (leaving plain exponential recursion intact).
- β’ Using 0 as an uninitialized sentinel when 0 is a valid computed answer (use map lookup ok or -1 sentinel instead).
- β’ Caching only successful branches and failing to memoize failed states (false/negative results repeat often too).
- β’ Confusing boolean visited arrays (cycle prevention) with computed memoization state caches.
- β’ Ignoring recursion stack depth limits (extremely deep recursions may require bottom-up tabulation).
- β’ Using non-comparable Go slice types directly as map keys instead of structs or integer bitmasks.
- β’ Mutating global state inside recursive calls that invalidates cached state assumptions.
Interview Rules
Recursion repeats work β Memoization (Top-Down DP)
Rule 1 β Define state parameters completely
Rule 2 β Check cache before recursive work
Rule 3 β Store computed answer before returning
Rule 4 β Cache both success and failure results
Rule 5 β In Go, use structs or matrices for multi-var keys
Time Complexity β Unique States Γ Transitions per State
Space Complexity β Memo Storage + Call Stack Depthπ‘ Golden Rule: If the same recursive state appears again, don't solve it againβreturn the answer you already stored.
Production Thinking
Dependency Evaluation & Build Graphs β evaluating shared sub-dependencies across DAG nodes (like Bazel or Make) uses memoization.
Recursive Parsers & AST Search β combinational parsers cache (position, token) states to eliminate redundant backtracking.
Demand-Driven State Queries β memoization computes only reachable states on-demand, saving memory over full table generation.
Production warning β algorithmic memoization is scoped to a single request run; production caches require TTL, eviction limits, and thread safety.
Remember This
Top-Down DP = Recursion + Memo Cache
Golden Order: Base Case β Memo Hit β Solve β Store β Return
Include ALL changing parameters in state key
Cache failed states (false / -1) as well as success states
Map lookup: val, ok := memo[key]
Time: States Γ Work Per State
Space: Memo Size + Stack Depth