Dynamic Programming
Dynamic programming solves problems that have overlapping subproblems and optimal substructure by solving each subproblem once and reusing the result. Top-down DP adds memoisation to recursion; bottom-up DP fills a table iteratively.
The hard part is defining the state: what exactly does dp[i] mean? Once the state and transition are clear, the code is short.
Steps
1) Define the state. 2) Write the transition (recurrence). 3) Set the base cases. 4) Decide the iteration order. 5) Optionally reduce space.
Classic problems
Fibonacci, climbing stairs, house robber, coin change, longest common subsequence, 0/1 knapsack and edit distance.
def climb(n):
dp = [0] * (n + 1)
dp[0] = dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
print(climb(5))8dp[i] = number of ways to reach step i.
def coin_change(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for c in coins:
for a in range(c, amount + 1):
dp[a] = min(dp[a], dp[a - c] + 1)
return -1 if dp[amount] == float('inf') else dp[amount]
print(coin_change([1, 3, 4], 6))2DP finds 3+3 where greedy would answer 3 coins.
Key points
- DP needs overlapping subproblems and optimal substructure.
- Memoisation is top-down; tabulation is bottom-up.
- Defining the state clearly is the main difficulty.
- Many 1D DP solutions can be reduced to O(1) space.
