Back to List

Dynamic Programming β€” DP and Memoization

Understand the core principles of dynamic programming, including overlapping subproblems and optimal substructure,

Intermediate
|
12min
|
Verified (2026-07)
dynamic programmingDPmemoizationtabulationoptimal substructure
Progress0/23 (0%)

Dynamic Programming β€” DP and Memoization

After This Topic

You will be able to identify situations where dynamic programming is needed and solve problems using both top-down (memoization) and bottom-up (tabulation) approaches.


Problems with Repeated Calculations

When implementing the Fibonacci sequence recursively:

python
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(35)) # 9227465 β€” about 4 seconds
print(fib(50)) # ... never finishes

When calculating fib(5), fib(3) is called twice, and fib(2) is called three times. For fib(50), the number of redundant calls reaches billions.

text
fib(5)
β”œβ”€β”€ fib(4)
β”‚   β”œβ”€β”€ fib(3)        ← calculated here
β”‚   β”‚   β”œβ”€β”€ fib(2)
β”‚   β”‚   └── fib(1)
β”‚   └── fib(2)        ← calculated again
└── fib(3)            ← calculated again
    β”œβ”€β”€ fib(2)        ← calculated again
    └── fib(1)

Time complexity: O(2^n). This problem can be solved by remembering the answers that have already been calculated.


Conditions for Dynamic Programming

Two things are required to apply DP:

1. Overlapping Subproblems

The same small problem is repeated multiple times, like fib(3) in the Fibonacci sequence.

2. Optimal Substructure

The optimal solution to a larger problem is composed of optimal solutions to smaller problems, like fib(n) = fib(n-1) + fib(n-2).

If both conditions are met, DP can be applied. If not, other methods (greedy, divide and conquer, etc.) should be used.


Top-Down β€” Memoization

Maintain the recursion from "top to bottom," but store the results of calculations in a dictionary (or array):

python
def fib(n, memo={}):
if n <= 1:
return n
if n in memo:
return memo[n]
memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
return memo[n]
print(fib(50)) # 12586269025 β€” completes immediately
print(fib(100)) # 354224848179261915075

fib(50) changes from 4 seconds to immediate. Each fib(k) is calculated only once, so the time complexity is O(n).

In Python, you can use functools.lru_cache to write it more cleanly:

python
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)

Memoization is completed with a single line of decorator.


Bottom-Up β€” Tabulation

Solve problems from "bottom to top," starting with smaller problems:

python
def fib(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]

Since no recursion is used, there is no risk of stack overflow. fib(10000) is also not a problem.

Space optimization: For Fibonacci, only the two most recent values are needed:

python
def fib(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b

O(n) time, O(1) space.


Top-Down vs. Bottom-Up

Top-Down (Memoization)Bottom-Up (Tabulation)
ApproachRecursion + CacheLoop + Table
ImplementationAdd cache to existing recursionConvert recurrence relation to loop
Calculates only necessary partsβœ…βŒ (Fills everything)
Stack OverflowPossible (when n is large)None
Space OptimizationDifficultEasy

In general, bottom-up is slightly faster (no function call overhead) and space optimization is easier. However, there are cases where converting the recurrence relation to a loop is not intuitive, so choose based on the situation.


Representative Problem: Climbing Stairs

There are n stairs, and you can climb 1 or 2 stairs at a time. How many ways are there to reach the top?

python
def climb_stairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1] = 1 # 1 step: 1 way
dp[2] = 2 # 2 steps: 1+1 or 2, 2 ways
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
print(climb_stairs(5)) # 8
print(climb_stairs(10)) # 89

To reach the i-th step, you can either climb 1 step from (i-1) or 2 steps from (i-2). dp[i] = dp[i-1] + dp[i-2] β€” the same structure as Fibonacci.


Representative Problem: 0-1 Knapsack

A knapsack with capacity W, and n items. Each item can either be included or excluded (0-1). Maximize the total value:

python
def knapsack(W, items):
n = len(items)
dp = [[0] * (W + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
weight, value = items[i - 1]
for w in range(W + 1):
dp[i][w] = dp[i - 1][w] # Not included
if w >= weight:
dp[i][w] = max(dp[i][w], dp[i - 1][w - weight] + value)
return dp[n][W]
items = [(2, 3), (3, 4), (4, 5), (5, 6)] # (weight, value)
print(knapsack(8, items)) # 10

dp[i][w] is the maximum value that can be achieved using the first i items with a capacity of w. For each item, record the maximum of the two options: "include" vs. "exclude."


DP Problem Solving Pattern

  1. State Definition: Clearly define what dp[i] represents.
  2. Recurrence Relation: Express dp[i] in terms of previous states.
  3. Base Case: Fill in the answers for the smallest problems.
  4. Order: Determine the order in which to fill the table.
  5. Result Extraction: Obtain the answer from dp[n] or max(dp).

In coding tests, a large number of problems that ask for "number of ways," "minimum/maximum," or "whether it is possible" are DP problems. If the input size is in the hundreds to thousands, suspect O(nΒ²) DP.


Distinguishing DP from Non-DP Problems

SignalDP Possibility
"Do ~ with minimum/maximum cost"High
"Find the number of ways to do ~"High
"Determine whether it is possible to do ~"High
"Problem that can be solved by sorting and selecting"Greedy first
"Problem that requires exploring all paths"DFS/BFS first

Both DP and greedy algorithms utilize "optimal substructure," but greedy algorithms go in one direction without backtracking, while DP records the results of all possible choices in a table.


Key Takeaway: Dynamic programming is a strategy of "not repeating the same calculations." It is implemented in two ways: memoization (top-down, recursion + cache) and tabulation (bottom-up, loop + table), and it reduces O(2^n) to O(n) or O(nΒ²).

πŸ’¬ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...