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. 1.Fibonacci Number
    easy
  2. 2.Climbing Stairs
    easy
  3. 3.House Robber
    medium
  4. 4.Coin Change
    medium
  5. 5.Word Break
    medium
  6. 6.Decode Ways
    medium
  7. 7.Unique Paths
    medium
  8. 8.Minimum Path Sum
    medium
  9. 9.Longest Common Subsequence
    medium
  10. 10.Edit Distance
    medium
  11. 11.Partition Equal Subset Sum
    medium
  12. 12.Target Sum
    medium
  13. 13.Longest Increasing Subsequence
    medium
  14. 14.Combination Sum IV
    medium
  15. 15.Perfect Squares
    medium
  16. 16.Interleaving String
    medium
  17. 17.Longest Palindromic Subsequence
    medium
  18. 18.House Robber III
    medium
  19. 19.Longest Increasing Path in a Matrix
    hard
  20. 20.Different Ways to Add Parentheses
    medium

Also Important

10 advanced memoization variations worth practicing.

  1. 21.Burst Balloons
    hard
  2. 22.Stone Game
    medium
  3. 23.Regular Expression Matching
    hard
  4. 24.Distinct Subsequences
    hard
  5. 25.Dungeon Game
    hard
  6. 26.Scramble String
    hard
  7. 27.Palindrome Partitioning variants
    medium
  8. 28.DP on Trees
    medium
  9. 29.DFS + Memoization on DAG
    medium
  10. 30.Bitmask DP with Memoization
    hard

How to Think

Convert slow top-down recursion into optimal DP by checking the memo cache before exploring subproblems.

  1. Recursive solution works but is too slow?Memoization
  2. Same function arguments appear again?Cache that state
  3. Need Top-Down DP?Recursion + Memo
  4. State has multiple variables?Struct / Matrix Key
  5. 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!

Visual Memory Rule
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 Depth

Memoization 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