Dynamic Programming
18 min
You already met dynamic programming's core idea in the Recursion course: memoization, caching subproblem results so nothing gets recomputed. DP formalizes that into two flavors:
- Top-down: plain recursion + a memo dict (what
fib_memodid). - Bottom-up: an iterative loop that fills a table from the smallest subproblem up to the answer — usually the more common style in practice, and avoids recursion-depth limits entirely.
A problem is a DP candidate when it has:
- Overlapping subproblems — the same smaller subproblem gets solved multiple times in a naive approach (fib(5) needs fib(3) twice).
- Optimal substructure — the optimal answer can be built from optimal answers to subproblems.
Reaching step n only ever comes from step n-1 (one step) or step n-2 (two steps), so ways(n) = ways(n-1) + ways(n-2) — the Fibonacci recurrence in disguise.
For problems with more than one input dimension, the table becomes an
array indexed by the subproblem parameter. Coin change: dp[a] holds the
fewest coins to make amount a, built up from dp[0] = 0:
def coin_change(coins, amount):
dp = [0] + [float("inf")] * amount
for a in range(1, amount + 1):
for c in coins:
if c <= a:
dp[a] = min(dp[a], dp[a - c] + 1)
return dp[amount] if dp[amount] != float("inf") else -1
Every dp[a] only depends on smaller dp[a - c] values that were already
computed earlier in the loop — that ordering is what makes bottom-up work
without recursion.
Write a function `climb_stairs(n)` that returns the number of distinct ways to climb `n` stairs, taking either 1 or 2 steps at a time. Use bottom-up DP (an iterative loop, not naive recursion).
Write a function `coin_change(coins, amount)` that returns the fewest number of coins from `coins` (unlimited supply of each) needed to make exactly `amount`, or -1 if it's impossible.
What two properties must a problem have for dynamic programming to apply?
You can recognize overlapping subproblems + optimal substructure, and build a bottom-up DP table for both 1D and coin-change-style problems.