Dynamic Programming
Solve complex problems by breaking them into overlapping subproblems and caching results.
The Mathematics
Optimal substructure, overlapping subproblems, and the Fibonacci example
Two Key Properties
Dynamic Programming applies when a problem has:
1. Optimal Substructure: An optimal solution contains optimal solutions to subproblems.
2. Overlapping Subproblems: The same subproblems are solved repeatedly in a naive recursive approach.
Fibonacci: The Classic Example
The Fibonacci sequence is defined by the recurrence:
This has both properties: depends on smaller subproblems (), and the same values are recomputed many times.
Naive Recursion: Exponential
A direct recursive implementation computes the same subproblems repeatedly. The call tree grows exponentially:
For , the naive approach makes 41 calls. alone is computed 5 times!
Bottom-Up DP: Linear
Instead, fill a table from the base cases up. Each subproblem is computed exactly once:
DP trades space for time: by storing values in a table, we eliminate all redundant computation. This is the essence of memoization (top-down) or tabulation (bottom-up).
See It Work
Computing Fibonacci(7) with bottom-up DP
Compute Fibonacci(7) using bottom-up DP. Allocate a table of size 8 to store subproblem results.
The Code
Mapping the Fibonacci recurrence to bottom-up Python
Mathematical Formulation
Base case: F(0) = 0 and F(1) = 1
Allocate array to store solutions to all subproblems
Fill table bottom-up using the recurrence relation
Each subproblem computed once — linear time and space
Naive recursion: exponential due to overlapping subproblems
Python Implementation
def fib_dp(n):
if n <= 1: return n
dp = [0] * (n + 1) # Allocate table
dp[0], dp[1] = 0, 1 # Base cases
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2] # Recurrence
return dp[n] # Answer
# Naive recursive (exponential!)
def fib_naive(n):
if n <= 1: return n
return fib_naive(n-1) + fib_naive(n-2)