Phase 5 · Dynamic ProgrammingModule 15~44 min read

Introduction to Dynamic Programming

The single most powerful technique in the course: solve each overlapping subproblem once and reuse the answer.

What you'll learn

Dynamic programming (DP) is the most powerful technique in this course. The idea is almost embarrassingly simple: when a problem breaks into smaller subproblems that overlap, solve each one only once and remember the answer. That single move can turn an exponential algorithm into a linear one.

By the end you'll be able to:

  • Recognize the two properties that make a problem a DP problem
  • Write both top-down (memoized) and bottom-up (tabulated) solutions
  • Follow a repeatable 4-step recipe to design any DP

The problem with plain recursion

Take the Fibonacci numbers, where each is the sum of the previous two. The natural recursion is a one-liner — and a performance disaster:

Language
fib_naive.py
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)   # recomputes the same calls

The trouble is that fib(n - 1) and fib(n - 2) re-explore the same smaller calls again and again. Computing fib(5) already calls fib(2) three times; the total work grows like O(φⁿ) ≈ O(1.618ⁿ).

The same subproblems, over and over

Naive recursion — times computed

fib(0)×3
fib(1)×5
fib(2)×3
fib(3)×2
fib(4)×1
fib(5)×1

15 calls just for fib(5) — and it explodes exponentially.

Dynamic programming — once each

fib(0)×1
fib(1)×1
fib(2)×1
fib(3)×1
fib(4)×1
fib(5)×1

6 computations for fib(5) — linear in n.

Naive recursion recomputes overlapping subproblems; DP computes each once.

The two properties

Dynamic programming applies exactly when a problem has both of these:

  • Overlapping subproblems. The same subproblems are solved many times (like fib(2) above). If every subproblem were distinct, there'd be nothing to reuse.
  • Optimal substructure. An optimal answer is built from optimal answers to its subproblems — so combining sub-answers is valid.

Key idea

Overlapping subproblems is what makes DP faster than plain recursion; optimal substructure is what makes it correct. You need both.

Memoization: top-down

The smallest possible fix: keep the recursion, but cache each answer the first time you compute it. This is memoization — top-down DP. The shape of the code barely changes:

Language
fib_memo.py
def fib(n, memo={}):
    if n < 2:
        return n
    if n not in memo:                     # compute only once
        memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
    return memo[n]

Now each fib(k) is computed once and reused, so the running time collapses from exponential to O(n). The cost is an O(n) cache plus the recursion stack.

Tabulation: bottom-up

Flip it around: instead of recursing down from n, fill a table up from the base cases. This is tabulation — bottom-up DP. Watch the table fill, one cell at a time. Each new cell depends on the two before it (highlighted), which are already computed:

Filling the Fibonacci DP table
Fibonacci by tabulation
0
1
2
3
4
5
6
7
8
9
·
·
·
·
·
·
·
·
·
·

dp[i] = dp[i-1] + dp[i-2]

computing depends on answer
10 columns
1/20Goal: fill dp[0..9], where dp[i] is the i-th Fibonacci number.
dp[i] = dp[i-1] + dp[i-2]. Each cell is computed once, then reused — never recomputed.
Language
fib_dp.py
def fib(n):
    if n < 2:
        return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]   # dependencies already filled
    return dp[n]

Tip

Bottom-up avoids recursion entirely, so there's no stack to overflow. And since dp[i] only needs the last two values, you can drop the whole array and keep just two variables — O(1) space.

The DP recipe

Every dynamic program — no matter how advanced — is designed with the same four questions. Keep this checklist handy for the rest of the phase:

Designing any DP in four steps
1

State

Decide what dp[i] means — the answer to a subproblem.

2

Transition

Write dp[i] in terms of smaller subproblems (the recurrence).

3

Base cases

Fill the smallest subproblems directly.

4

Order

Fill so every dependency is ready before you need it.

For Fibonacci: the state is dp[i] = the i-th number; the transition is dp[i] = dp[i-1] + dp[i-2]; the base cases are dp[0]=0, dp[1]=1; and the order is simply increasing i.

Complexity

ApproachTimeSpaceNotes
Naive recursionO(φⁿ)O(n)Recomputes subproblems — exponential
Top-down (memo)O(n)O(n)Each subproblem once; uses recursion stack
Bottom-up (table)O(n)O(n)Iterative; O(1) space if you keep the last two

Note

Memoization and tabulation do the same amount of real work — they compute each subproblem once. Pick top-down when the recursion is natural and not every state is needed; pick bottom-up for speed, no stack limits, and easy space optimization.

Recap & quick check

Key takeaways

  • DP solves overlapping subproblems once and reuses the answers.
  • It applies when a problem has overlapping subproblems AND optimal substructure.
  • Top-down = recursion + a cache (memoization); bottom-up = fill a table (tabulation).
  • Both turn exponential Fibonacci into O(n).
  • Design any DP with the recipe: state, transition, base cases, fill order.

Quick check

1. What two properties make a problem suitable for dynamic programming?

2. What is the difference between memoization and tabulation?

3. Why is naive recursive Fibonacci so slow?

4. In the DP recipe, what does the 'transition' describe?

You've got the core idea. Now let's drill the most common one-dimensional patterns until they're automatic. Next up: Module 16 — 1-D Dynamic Programming.