Dynamic Programming Fundamentals
Break a problem into overlapping subproblems, solve each once, cache the result β turning exponential brute force into polynomial time. The most feared interview topic, but once you see the pattern, every DP problem is the same three steps: state, recurrence, base case.
What is Dynamic Programming?
Dynamic programming is an optimisation technique that applies when a problem has two properties: overlapping subproblems (the same subproblem is solved multiple times in a naive recursive solution) and optimal substructure (the optimal solution to the problem contains optimal solutions to its subproblems). The technique works by solving each subproblem once and caching the result β subsequent calls look up the cached value instead of recomputing. This turns the exponential branching of naive recursion into polynomial time. There are two approaches: top-down (memoisation) β write the natural recursion and add a cache, and bottom-up (tabulation) β fill a table iteratively from the smallest subproblems upward. Both produce the same result; bottom-up is typically faster (no recursion overhead) and easier to optimise for space. Every DP problem reduces to three decisions: (1) what is the STATE β the parameters that uniquely identify a subproblem; (2) what is the RECURRENCE β how the current state relates to smaller states; (3) what is the BASE CASE β the smallest state with a known answer.
How It Thinks
Top-Down β Memoisation
Start from the original problem, recurse into subproblems, cache results. The code reads like the mathematical recurrence. Easiest to write β start with brute-force recursion, then add a memo array.
| Step | Question | Fibonacci Example |
|---|---|---|
| 1. State | What parameters define a subproblem? | n β the index |
| 2. Recurrence | How does current state relate to smaller states? | fib(n) = fib(n-1) + fib(n-2) |
| 3. Base case | Smallest state with a known answer? | fib(0)=0, fib(1)=1 |
- It only computes subproblems that are actually needed.
- If the subproblem graph is sparse (many states exist but few are reachable), top-down avoids computing unnecessary states.
- Bottom-up computes ALL states in the table whether needed or not.
Bottom-Up β Tabulation
Fill a table iteratively from the base cases upward. No recursion, no stack overflow risk. The iteration order must respect dependencies β each state should only depend on states already computed. Bottom-up is typically preferred in interviews for its simplicity and speed, and it enables space optimisation.
Recursive + Cache
- Write natural recursion first
- Add memo array/map
- Only computes reachable states
- Risk: stack overflow on deep recursion
- Easier to reason about
Iterative + Table
- Fill table from base cases upward
- No recursion β no stack overflow
- Computes ALL states in the table
- Enables space optimisation
- Preferred in interviews
1D Dynamic Programming
The state is a single index or value β dp[i] represents the answer for the problem considering only the first i elements (or reaching position i). Most beginner DP problems are 1D.
- At each element, you either take it (and add its value to the best achievable from a non-conflicting position) or skip it (and carry forward the best achievable so far).
- Climbing Stairs, House Robber, Coin Change, and Jump Game all follow this structure.
2D Dynamic Programming
The state has two dimensions β dp[i][j] might represent "the answer considering the first i items with capacity j" (knapsack) or "the answer for substring s[i..j]" (interval DP) or "the answer at grid position (i,j)" (path problems). 2D DP is common in medium/hard interview problems.
- If dp[i][j] only depends on row i-1 and row i, you only need to keep two rows (or even one row, filling right-to-left).
- This reduces O(mΓn) space to O(n).
- For LCS:
int[] prev = new int[n+1], curr = new int[n+1]β swap after each row.
DP Pattern Families
| Pattern | State | Recurrence Shape | Examples |
|---|---|---|---|
| Linear (1D) | dp[i] = answer for first i elements | dp[i] = f(dp[i-1], dp[i-2], ...) | Fibonacci, Stairs, House Robber |
| Knapsack | dp[i][w] = best value with items 0..i, capacity w | dp[i][w] = max(skip, take + dp[i-1][w-wt]) | 0/1 Knapsack, Coin Change, Partition Equal Subset |
| Grid paths | dp[i][j] = answer at cell (i,j) | dp[i][j] = f(dp[i-1][j], dp[i][j-1]) | Unique Paths, Min Path Sum |
| String matching | dp[i][j] = answer for s1[0..i], s2[0..j] | match β diagonal + 1; no match β max(up, left) | LCS, Edit Distance, Interleaving |
| Interval | dp[i][j] = answer for subarray i..j | dp[i][j] = f(dp[i][k], dp[k+1][j]) for k in i..j | Burst Balloons, Matrix Chain |
| Subsequence | dp[i] = longest ending at index i | dp[i] = max(dp[j]+1) for j < i where arr[j] < arr[i] | LIS, Max Sum Increasing Subseq |
Build It Once
Build each classic DP pattern once. The hardest part is identifying the state and recurrence β once you have those, the code is mechanical.
Use It In Java
int[] dp = new int[n + 1]; β most common, 0-indexed or 1-indexed int[][] dp = new int[m + 1][n + 1]; β string/grid problems Map<String, Integer> memo = new HashMap<>(); β multi-key states | Idiom | Use case | Note |
|---|---|---|
Arrays.fill(dp, value) | Initialise DP array | Use Integer.MAX_VALUE for min problems, 0 for count problems |
dp[0] = base_value | Base case | dp[0] = 0 for coin change, dp[0] = 1 for path counting |
Math.max / Math.min | Recurrence | Maximise or minimise at each state |
int prev2, prev1, curr | Space optimisation | Replace dp array when only 2 previous values needed |
for (w = W; w >= wt; w--) | 0/1 Knapsack (1D) | Iterate right-to-left to avoid using same item twice |
int[] prev, curr; swap | 2D β 1D space opt | Keep only the previous row for 2D problems |
for w = W down to wt). This ensures each item is used at most once. If you iterate left to right, the same item can be used multiple times β that's the unbounded knapsack variant, not 0/1.
Integer.MAX_VALUE as the "impossible" sentinel in min problems (like Coin Change), adding 1 to it causes integer overflow β wraps to Integer.MIN_VALUE. Guard with: if (dp[a-c] != Integer.MAX_VALUE) dp[a] = min(dp[a], dp[a-c]+1). Or use amount + 1 as sentinel instead.
Problems To Solve
DP interview problems are pattern-based β once you've solved one problem in a family, others in the same family are variations. Start with 1D problems (Climbing Stairs, House Robber), then move to Knapsack variants (Coin Change, Partition Equal Subset), then string DP (LCS, Edit Distance). The hardest step is always defining the state β once you have the state and recurrence, the code is mechanical.
| Difficulty | Pattern | Problem | Key Insight |
|---|---|---|---|
| Easy | 1D linear | Climbing Stairs β LC 70 | dp[i] = dp[i-1] + dp[i-2]. Identical to Fibonacci. Space-optimise to O(1) with two variables. |
| Medium | 1D take/skip | House Robber β LC 198 | dp[i] = max(dp[i-1], dp[i-2] + nums[i]). Take or skip each house. Space-optimise to O(1). |
| Medium | knapsack | Coin Change β LC 322 | dp[a] = min(dp[a-coin]+1) for each coin. Unbounded knapsack variant. Sentinel: amount+1 for impossible. |
| Medium | subsequence | Longest Increasing Subsequence β LC 300 | dp[i] = max(dp[j]+1) for j < i where nums[j] < nums[i]. O(nΒ²). Binary search variant achieves O(n log n). |
| Medium | string | Longest Common Subsequence β LC 1143 | dp[i][j] = match β diagonal+1, else max(up, left). Space-optimise with two rows. |
| Medium | string | Edit Distance β LC 72 | dp[i][j] = min cost to convert s1[0..i] to s2[0..j]. Three operations: insert, delete, replace. Match β free (diagonal). |
- When you see "count the number of ways," "minimum/maximum cost," "can you reach / partition / make amount X" β think DP.
- The three steps: (1) Define the STATE β what parameters uniquely identify a subproblem. (2) Write the RECURRENCE β how dp[i] depends on smaller states. (3) Set the BASE CASE β the smallest state with a known answer.
- Then code it bottom-up with a for loop. Optimise space if dp[i] only needs the previous 1β2 values.
β See Dynamic Programming β 1D problems with full solutions
β See Dynamic Programming β 2D / Advanced problems with full solutions