Skip to content

Dynamic Programming

Key idea — DP is recursion + caching. Once you write the recurrence relation (same skill as Trees & Recursion), you either memoize top-down (cache recursive calls) or tabulate bottom-up (fill a table iteratively). The hard part is defining the state.

When to use — Overlapping sub-problems + optimal substructure: longest subsequence, knapsack, coin change, edit distance, number of ways to reach a target. If brute-force recursion revisits the same (i, j, ...) state, DP applies.

Two approaches

Top-down (Memoization)Bottom-up (Tabulation)
StyleRecursive + cache mapIterative + dp slice/table
OrderNatural (call what you need)Must determine fill order
SpaceCall stack overheadOften easier to optimize

Pattern / template

go
// Top-down memoization
var memo = map[[2]int]int{}

func dp(i, j int) int {
    if i == 0 { return BASE }          // base case
    if v, ok := memo[[2]int{i, j}]; ok {
        return v
    }
    // recurrence relation
    result := max(dp(i-1, j), dp(i, j-1))
    memo[[2]int{i, j}] = result
    return result
}

// Bottom-up tabulation
func dpTable(m, n int) int {
    table := make([][]int, m+1)
    for i := range table {
        table[i] = make([]int, n+1)
    }
    for i := 1; i <= m; i++ {
        for j := 1; j <= n; j++ {
            table[i][j] = max(table[i-1][j], table[i][j-1])
        }
    }
    return table[m][n]
}

Complexity

  • Time: O(states × work per state) — depends on state definition.
  • Space: O(states); often reducible to O(1 row) with rolling array.

Pitfalls

  • Wrong state definition — if two distinct situations map to the same state key, results are incorrect.
  • Off-by-one in base cases (0-indexed vs 1-indexed dp table).
  • Not recognising the sub-problem: if you can't articulate the recurrence, step back to the recursive tree.

Common follow-ups

  • Can you reduce the space complexity with a rolling array?
  • How do you reconstruct the actual solution (not just the optimal value)?
  • What is the state definition, and why is it sufficient to capture all distinct sub-problems?
  • How would you approach this top-down vs bottom-up, and what are the trade-offs?

Practice (LeetCode)

  • LC 70 — Climbing Stairs
  • LC 198 — House Robber
  • LC 322 — Coin Change
  • LC 300 — Longest Increasing Subsequence
  • LC 1143 — Longest Common Subsequence
  • LC 72 — Edit Distance