Fibonacci and Memoization
From recursion that solves the same subproblems over and over to memoization and tables that compute each value once.
#How does it work?
Dynamic programming (DP) splits a problem into overlapping subproblems and solves each subproblem only once. Fibonacci numbers (0, 1, 1, 2, 3, 5, 8, …) are the easiest way to see it: fib(n) = fib(n − 1) + fib(n − 2).
Turning the definition straight into recursion gives the right answer but computes the same values again and again. For fib(5), fib(2) is computed three times and fib(3) twice; the number of calls grows exponentially, and fib(50) needs billions of calls.
Memoization (top-down DP) keeps the recursion but writes every result to a cache and returns it without recomputing when it is asked for again. The table method (bottom-up DP) drops recursion altogether and fills an array from small values to large ones.
A problem fits DP when it has two properties: overlapping subproblems (the same subproblem shows up again and again) and optimal substructure (the big answer is built from the answers to the subproblems).
#Try it yourself
Compute the same fib(n) three ways. In plain recursion the red nodes are calls that recompute a value that was already computed.
#Step by step
- 01Define the state: dp[i] = the i-th Fibonacci number.
- 02Write the base cases: dp[0] = 0, dp[1] = 1.
- 03Write the transition: dp[i] = dp[i − 1] + dp[i − 2].
- 04Pick an evaluation order: memoization (recursion + cache) or a table (a loop from small to large).
#Example code
The same algorithm in 8 languages. Every program is compiled, run and checked to print the same output.
def fib_naive(n: int) -> int:
"""Plain recursion: correct, but O(2^n) calls."""
return n if n < 2 else fib_naive(n - 1) + fib_naive(n - 2)
memo = {}
def fib_memo(n: int) -> int:
"""Top-down DP: every value is computed once and cached."""
if n < 2:
return n
if n not in memo:
memo[n] = fib_memo(n - 1) + fib_memo(n - 2)
return memo[n]
def fib_table(n: int) -> int:
"""Bottom-up DP with only the last two values: O(n) time, O(1) memory."""
a, b = 0, 1 # fib(0), fib(1)
for _ in range(n):
a, b = b, a + b
return a
print(fib_naive(10)) # fine for small n; fib_naive(40) would take seconds
print(fib_memo(40))
print(fib_table(50))
# Python can also memoize for you: functools.lru_cache on fib_naive.#Complexity
| Operation | Time |
|---|---|
| Plain recursion | O(2ⁿ) time |
| Memoization | O(n) time, O(n) memory |
| Table | O(n) time, O(n) memory |
| Last two values only | O(n) time, O(1) memory |
Plain recursion makes 2·fib(n + 1) − 1 calls, growing at about 1.6ⁿ. DP computes each value once, so it is linear. With matrix exponentiation fib(n) can even be computed in O(log n).
#When to use it?
Memoization is the first thing to try whenever a recursive solution is easy to write but repeats subproblems. The table method avoids deep recursion (stack overflow) and makes it easy to shrink memory. Climbing stairs, counting paths and coin-change counting all follow the same pattern.
#Practice
Log in to answer questions and save your progress.
Easy
Why is plain recursive fib(n) slow?
- 1It recomputes the same values again and again
- 2Addition is slow
- 3It allocates too much memory
- 4It doesn't use a loop
What is memoization?
- 1Caching computed results and returning them when asked again
- 2Turning recursion into a loop
- 3Sorting the array
- 4Writing results to a file
What is fib(n)'s time complexity with memoization?
- 1O(n)
- 2O(2ⁿ)
- 3O(n²)
- 4O(log n)
Medium
How many calls does plain recursion make to compute fib(5)?
- 115
- 25
- 38
- 425
Hard
Which two properties make a problem suitable for dynamic programming?
- 1Overlapping subproblems and optimal substructure
- 2Sorted input and fixed size
- 3Recursion and loops
- 4Greedy choice and backtracking
#Up next
Lessons that build on this one: