Pattern 06
Dynamic Programming
Break a hard problem into smaller repeated decisions and store results.
When to use
Use when a problem has overlapping subproblems — you'd compute the same state multiple times in a naive approach. Also when you need to find the optimal (max/min/count) of something across choices.
Core intuition
Define a state that captures everything relevant at a point in the problem. Write a recurrence: how does dp[i] relate to dp[i-1] or dp[i][j-1]? Then either memoize top-down or fill a table bottom-up.
Template
# Top-down memoization
from functools import lru_cache
def solve(n):
@lru_cache(maxsize=None)
def dp(i):
if i <= 1:
return i
return dp(i-1) + dp(i-2)
return dp(n)
# Bottom-up tabulation
def climb_stairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1], dp[2] = 1, 2
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]Suggested problems
Key takeaways
- ›DP means: what is the best answer up to this point?
- ›Define your state clearly before writing any code
- ›Write the recurrence: how does state i depend on earlier states?
- ›Top-down = recursion + cache. Bottom-up = fill a table.
- ›If you can write the recurrence, you are 80% done.
You'll see this when...
- ›Count the number of ways to do something
- ›Find the minimum cost / maximum profit
- ›Can we reach X? How many ways?
- ›String matching or subsequence problems
- ›Knapsack / subset sum style decisions