Dynamic programming: Fibonacci, knapsack and memoization
In short: Dynamic programming solves problems with overlapping subproblems by storing intermediate results instead of recomputing them; this turns exponential computations into polynomial ones.
In more detail: If a recursive solution solves the same subproblems again and again, you store the results. Two ways: memoization (top-down: recursion + cache) and tabulation (bottom-up: fill a table).
In Depth
Example Fibonacci
fib(n) = fib(n−1) + fib(n−2). Naively recursive, fib(40) takes over 300 million calls (exponential, O(2ⁿ)) because sub-values are computed repeatedly.
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n): # memoization: O(n)
return n if n < 2 else fib(n - 1) + fib(n - 2)
def fib_tab(n): # tabulation/bottom-up: O(n), O(1) space
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return aDeep recursion reaches Python’s recursion limit; bottom-up is then better.
When is DP applicable?
Two properties: optimal substructure (solution composed of optimal sub-solutions) and overlapping subproblems. Procedure: 1) define the state (what describes a subproblem?), 2) formulate the recurrence/transition, 3) base cases, 4) order of computation (table), 5) read out the answer, possibly reconstruct the path. Greedy algorithms are faster but only correct if the greedy choice is provably optimal; DP is safer.
Classics
- Knapsack problem (0/1): items with weight/value, capacity W:
dp[i][w] = max(dp[i−1][w], dp[i−1][w−weight_i] + value_i); time O(n·W) (pseudo-polynomial). - Fewest coins (coin change):
dp[x] = 1 + min(dp[x − m]). - Longest common subsequence (LCS) and edit distance (Levenshtein): basis of
diff, spell checking, DNA comparison. - Shortest paths (Bellman-Ford, Floyd-Warshall).
- Staircase, counting problems, matrix chains, optimal search trees, Viterbi algorithm (speech recognition).
Practice and exam
- Draw the table and fill it by hand: that creates understanding.
- Save space: often the last row/two values suffice.
- Weigh memory/time; O(n·W) can get large.
- Typical errors: wrong base cases, off-by-one, loop order.
- In programming contests, optimisation (routes, cuts, planning), bioinformatics, text comparison.
- Related: recursion, backtracking (tries all paths), greedy, divide and conquer (subproblems do not overlap).
See also: Recursion, Greedy algorithms, Big-O notation