Dynamic Programming
Key Patterns
1. 1D DP
- When: Single sequence, linear decisions
- Examples: Climbing stairs, house robber, coin change
2. 2D DP
- When: Two sequences, grid problems
- Examples: LCS, edit distance, unique paths
3. Knapsack
- 0/1 Knapsack: Take or skip each item
- Unbounded: Can take item multiple times
4. Interval DP
- When: Optimal way to split/merge intervals
- Examples: Matrix chain multiplication, burst balloons
Framework
- Define state: What information do we need?
- Define recurrence: How do states relate?
- Base cases: Starting values
- Order of computation: Bottom-up or top-down
- Extract answer: Where is the final result?
Problems by Pattern
1D DP
| Problem |
Difficulty |
Link |
Status |
| Climbing Stairs |
Easy |
|
|
| House Robber |
Medium |
|
|
| Coin Change |
Medium |
|
|
| Longest Increasing Subsequence |
Medium |
|
|
2D DP
| Problem |
Difficulty |
Link |
Status |
| Unique Paths |
Medium |
|
|
| Longest Common Subsequence |
Medium |
|
|
| Edit Distance |
Medium |
|
|
Knapsack
| Problem |
Difficulty |
Link |
Status |
| Partition Equal Subset Sum |
Medium |
|
|
| Target Sum |
Medium |
|
|
Common Techniques
# 1D DP (bottom-up)
dp = [0] * (n + 1)
dp[0] = base_case
for i in range(1, n + 1):
dp[i] = recurrence(dp[i-1], dp[i-2], ...)
# 1D DP (top-down with memoization)
@lru_cache(maxsize=None)
def dp(i):
if base_case:
return value
return recurrence(dp(i-1), dp(i-2), ...)
# 2D DP
dp = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, m + 1):
dp[i][j] = recurrence(dp[i-1][j], dp[i][j-1], ...)
# Space optimization (2D -> 1D)
prev = [0] * (m + 1)
curr = [0] * (m + 1)
for i in range(1, n + 1):
for j in range(1, m + 1):
curr[j] = recurrence(prev[j], curr[j-1], ...)
prev, curr = curr, prev
Tips
- Start with brute force recursion
- Identify overlapping subproblems
- Add memoization (top-down)
- Convert to tabulation (bottom-up) if needed
- Optimize space if possible