Recognizing DP problems
We've reached the last and most feared section. But here's a secret: you've already done DP in this course. Kadane (Array) and recursion on trees were dynamic programming in disguise. DP is just recursion that doesn't repeat work.
As always, each pattern lesson opens with a π― Try it first.
What dynamic programming is
DP solves a problem by breaking it into smaller subproblems and reusing the answers to those subproblems instead of recomputing them. Two conditions must be present:
- Overlapping subproblems: the same subproblem shows up many times.
- Optimal substructure: the problem's answer is built from the answers to the subproblems.
The canonical example is Fibonacci. The naive recursion recomputes the same values an exponential number of times:
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1) β fib(3) and fib(2) recomputed!
Run it and see the waste β the naive version makes thousands of calls; the one with memoization (remembering what it already computed) makes a few dozen:
Memoization vs tabulation: the two flavors of DP
There are two ways to do DP, and they give the same answer:
| Memoization (top-down) | Tabulation (bottom-up) | |
|---|---|---|
| How | recursion + a cache of the results | a loop filling a dp[] table |
| Direction | from the big problem to the small ones | from the small ones to the big one |
| Advantage | closer to the natural recursion | no recursion stack; easy to optimize space |
Most lessons will use tabulation (the dp table), because it makes the
recurrence explicit and visible β and you get to watch the table fill in.
The method for solving any DP
Faced with a DP problem, answer three questions:
- What is the state? What does
dp[i](ordp[i][j]) mean? (e.g., "the maximum using the first i elements".) - What is the recurrence? How is
dp[i]built from smaller states? (e.g.,dp[i] = max(dp[i-1], ...).) - What are the base cases? The initial values that don't depend on anyone.
With these three defined, the code almost writes itself.
Big-O in DP: states Γ cost per state
Here is the section's Big-O deep dive, and the most useful shortcut for estimating the complexity of a DP:
Time β (number of states) Γ (cost to compute each state).
- 1D DP with an O(1) recurrence:
nstates Γ O(1) = O(n). - 2D DP (two strings of sizes m and n):
mΒ·nstates Γ O(1) = O(mΒ·n). - Coin change:
amountstates Γ O(coins) per state = O(amount Β· coins).
And the space is the size of the table β often you can reduce it (if
dp[i] only depends on dp[i-1], you keep two variables instead of the whole
array, dropping to O(1)). That's why memoization trades exponential time for
polynomial: it limits the work to the number of distinct states.
The section's problems
- Climbing Stairs, House Robber β 1D DP, include/exclude
- Coin Change, Combination Sum IV β knapsack
- Unique Paths β grid DP
- Longest Common Subsequence, LIS, Word Break, Decode Ways β DP on sequences/strings
Summary
- DP = recursion without repeating work; needs overlapping subproblems + optimal substructure.
- Memoization (recursion + cache) and tabulation (loop filling
dp) give the same result. - Method: define the state, recurrence, and base cases.
- Time β states Γ cost per state; space is the table (often reducible).
Next lesson: the simplest recurrence of all β 1D DP.