Master Fibonacci and Fibonacci-like problems with dynamic programming — naive recursion O(2^n), memoization O(n), tabulation O(n), space-optimized O(1) space, matrix exponentiation O(log n), and classic DP problems that reduce to Fibonacci variants like climbing stairs, house robber, and tile tiling.
Why Fibonacci Is the Perfect First DP Problem
The Fibonacci sequence (0, 1, 1, 2, 3, 5, 8, 13, 21, ...) is the gateway to dynamic programming. It perfectly demonstrates:
- Why naive recursion is exponential
- How memoization reduces it to linear
- How tabulation eliminates recursion
- How space optimization reduces memory
- How matrix exponentiation achieves O(log n)
More importantly, dozens of real DP problems are fundamentally Fibonacci variants — once you understand Fibonacci deeply, you recognize them instantly.
Level 2 — Memoization: O(n) time, O(n) space
from functools import cache
@cache
def fib_memo(n):
if n <= 1: return n
return fib_memo(n-1) + fib_memo(n-2)
print(fib_memo(100)) # 354224848179261915075 — instant
Each fib(i) computed exactly once → n total computations.
Level 3 — Tabulation: O(n) time, O(n) space
def fib_tab(n):
if n <= 1: return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(fib_tab(100)) # 354224848179261915075
Level 4 — Space Optimized: O(n) time, O(1) space
def fib_optimal(n):
"""Only need the last two values, not the full array."""
if n <= 1: return n
prev2, prev1 = 0, 1
for _ in range(2, n+1):
prev2, prev1 = prev1, prev2 + prev1
return prev1
print(fib_optimal(100)) # 354224848179261915075
This is the production solution for all Fibonacci-like 1D DP problems.
Level 5 — Matrix Exponentiation: O(log n)
For astronomically large n (n = 10^18), even O(n) is too slow. Matrix exponentiation computes fib(n) in O(log n):
def mat_mul(A, B, mod=None):
n = len(A)
C = [[0]*n for _ in range(n)]
for i in range(n):
for j in range(n):
for k in range(n):
C[i][j] += A[i][k] * B[k][j]
if mod: C[i][j] %= mod
return C
def mat_pow(M, n, mod=None):
k = len(M)
result = [[1 if i==j else 0 for j in range(k)] for i in range(k)]
while n > 0:
if n & 1: result = mat_mul(result, M, mod)
M = mat_mul(M, M, mod)
n >>= 1
return result
def fib_matrix(n, mod=None):
"""
Uses: [[fib(n+1)], [fib(n)]] = [[1,1],[1,0]]^n × [[1],[0]]
Time: O(8 log n) = O(log n)
"""
if n <= 1: return n
M = [[1,1],[1,0]]
result = mat_pow(M, n-1, mod)
return result[0][0]
print(fib_matrix(10)) # 55
print(fib_matrix(50)) # 12586269025
# For n=10^18, still instant (only ~60 matrix multiplications)
Fibonacci Variants — The Real Interview Problems
Problem 1 — Climbing Stairs
"Count ways to climb n stairs, taking 1 or 2 steps at a time."
def climb_stairs(n):
"""
dp[i] = ways to reach step i
dp[i] = dp[i-1] + dp[i-2] (arrive from step i-1 or i-2)
This IS Fibonacci!
dp[1]=1, dp[2]=2, dp[3]=3, dp[4]=5, dp[5]=8...
"""
if n <= 2: return n
prev2, prev1 = 1, 2
for _ in range(3, n+1):
prev2, prev1 = prev1, prev2 + prev1
return prev1
print([climb_stairs(i) for i in range(1, 8)])
# [1, 2, 3, 5, 8, 13, 21] ← Fibonacci sequence!
Problem 2 — Minimum Cost Climbing Stairs
def min_cost_stairs(cost):
"""
Each step has a cost. Pay cost[i] to step from i.
Can start from step 0 or 1. Reach the top (past last step).
"""
n = len(cost)
dp = [0] * (n + 1)
for i in range(2, n + 1):
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
return dp[n]
print(min_cost_stairs([10, 15, 20])) # 15
print(min_cost_stairs([1, 100, 1, 1, 1, 100, 1, 1, 100, 1])) # 6
Problem 3 — House Robber
"Rob houses, but cannot rob two adjacent houses. Maximize total."
def house_robber(nums):
"""
dp[i] = max money robbing houses 0..i
dp[i] = max(dp[i-1], # Don't rob house i
dp[i-2] + nums[i]) # Rob house i (skip i-1)
"""
if len(nums) == 1: return nums[0]
prev2, prev1 = nums[0], max(nums[0], nums[1])
for i in range(2, len(nums)):
prev2, prev1 = prev1, max(prev1, prev2 + nums[i])
return prev1
print(house_robber([1, 2, 3, 1])) # 4 (rob houses 0,2)
print(house_robber([2, 7, 9, 3, 1])) # 12 (rob houses 0,2,4)
print(house_robber([2, 1, 1, 2])) # 4
Problem 4 — House Robber II (Circular)
def house_robber_circular(nums):
"""
Houses in a circle: first and last are adjacent.
Solution: max(rob houses 0..n-2, rob houses 1..n-1)
"""
if len(nums) == 1: return nums[0]
def rob_linear(arr):
if len(arr) == 1: return arr[0]
prev2, prev1 = arr[0], max(arr[0], arr[1])
for i in range(2, len(arr)):
prev2, prev1 = prev1, max(prev1, prev2 + arr[i])
return prev1
return max(rob_linear(nums[:-1]), rob_linear(nums[1:]))
print(house_robber_circular([2,3,2])) # 3
print(house_robber_circular([1,2,3,1])) # 4
Problem 5 — Tile Tiling (Domino Tiling)
"How many ways to tile a 2×n board with 1×2 dominoes?"
def num_tilings(n):
"""
For 2×n board:
- If we place vertical domino at column n: dp[n-1] ways
- If we place two horizontal dominoes at columns n-1,n: dp[n-2] ways
dp[n] = dp[n-1] + dp[n-2] ← Fibonacci!
dp[1]=1, dp[2]=2, dp[3]=3, dp[4]=5...
"""
if n <= 2: return n
prev2, prev1 = 1, 2
for _ in range(3, n+1):
prev2, prev1 = prev1, prev2 + prev1
return prev1
print([num_tilings(i) for i in range(1, 8)])
# [1, 2, 3, 5, 8, 13, 21] ← Fibonacci!
Problem 6 — k-Steps Climbing Stairs (Tribonacci/Generalized)
def climb_stairs_k(n, k):
"""
Count ways to climb n stairs, taking 1 to k steps at a time.
For k=2: Fibonacci. For k=3: Tribonacci.
"""
if n == 0: return 1
dp = [0] * (n + 1)
dp[0] = 1
for i in range(1, n+1):
dp[i] = sum(dp[max(0, i-k):i])
return dp[n]
print(climb_stairs_k(5, 2)) # 8 (Fibonacci)
print(climb_stairs_k(5, 3)) # 13 (Tribonacci)
print(climb_stairs_k(5, 5)) # 16
The Fibonacci Pattern Recognition
Any DP problem of the form:
current = f(previous_1, previous_2, ...)
is a Fibonacci variant. Recognize it by:
- State only depends on a FIXED number of previous states
- No 2D table needed
- Space-optimized solution uses a few variables, not an array
| Fibonacci | dp[i] = dp[i-1] + dp[i-2] ← 2 previous |
| Tribonacci | dp[i] = dp[i-1] + dp[i-2] + dp[i-3] ← 3 previous |
| House Robber | dp[i] = max(dp[i-1], dp[i-2] + nums[i]) ← 2 previous |
All solved in O(n) time and O(1) space.
Summary
| Approach | Time | Space | Use When |
|---|
| Naive recursion | O(2^n) | O(n) | Never in production |
| Memoization | O(n) | O(n) | Quick implementation |
| Tabulation | O(n) | O(n) | Clear table structure |
| Space-optimized | O(n) | O(1) | Production code |
| Matrix exp. | O(log n) | O(1) | n up to 10^18 |
The Fibonacci problem is a microcosm of all 1D DP: recognize the recurrence, implement with rolling variables, achieve O(1) space.
*Next Lesson: Knapsack Problem*