Dynamic Programming (DP) is a technique for solving problems by breaking them into subproblems and storing results to avoid redundant computation. This post introduces DP tables, their use cases, and provides practical Python examples and problems for mastering dynamic programming.
Overview
Dynamic Programming (DP) is a technique used to solve problems by breaking them down into simpler subproblems and storing the results to avoid redundant computations.
A DP table is often used to store solutions to subproblems, typically in a 1D or 2D array.
- Top-down = recursion + memoization
- Bottom-up = iterative DP table filling
🧩 Visualizing Dynamic Programming
Fibonacci DP Table
graph LR
A[dp0] --> B[dp1]
B --> C[dp2]
C --> D[dp3]
D --> E[dp4]
E --> F[dp5]
style A fill:#ff9999
style B fill:#ff9999
style F fill:#99ff99
2D DP Table (Knapsack)
graph TD
subgraph "Knapsack DP Table"
subgraph "Capacity"
C0[0] --- C1[1] --- C2[2] --- C3[3] --- C4[4] --- C5[5]
end
subgraph "Items"
I0[Item 0<br/>w=1, v=15] --- I1[Item 1<br/>w=3, v=20] --- I2[Item 2<br/>w=4, v=30]
end
subgraph "DP Values"
V00[0] --- V01[0] --- V02[0] --- V03[0] --- V04[0] --- V05[0]
V10[0] --- V11[15] --- V12[15] --- V13[15] --- V14[15] --- V15[15]
V20[0] --- V21[15] --- V22[15] --- V23[20] --- V24[35] --- V25[35]
V30[0] --- V31[15] --- V32[15] --- V33[20] --- V34[35] --- V35[45]
end
end
style V35 fill:#99ff99
style V25 fill:#ffff99
style V15 fill:#ffcc99
🛠️ How to Use (Python)
# Example: Fibonacci with bottom-up DP
def fib(n):
if n <= 1:
return n
dp = [0] * (n + 1) # DP table to store results
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2] # Fill table iteratively
return dp[n]
# Time complexity: O(n), Space complexity: O(n)
# Example:
print(fib(5)) # Output: 5
print(fib(10)) # Output: 55
🧩 Climbing Stairs Step-by-Step
graph TD
S1[dp1 = 1 base case]
S2[dp2 = 2 base case]
S3[dp3 = dp2 + dp1 = 3]
S4[dp4 = dp3 + dp2 = 5]
R[Result: 5 ways]
S1 --> S3
S2 --> S3
S3 --> S4
S4 --> R
style R fill:#99ff99
Suppose n = 4
i | dp[i-1] | dp[i-2] | dp[i] = dp[i-1] + dp[i-2] | Ways to reach i |
---|---|---|---|---|
1 | - | - | 1 (base case) | [1] |
2 | - | - | 2 (base case) | [1,1], [2] |
3 | 2 | 1 | 2 + 1 = 3 | [1,1,1], [1,2], [2,1] |
4 | 3 | 2 | 3 + 2 = 5 | [1,1,1,1], [1,1,2], [1,2,1], [2,1,1], [2,2] |
- Final result: 5 ways to climb 4 stairs.
📘 Sample Problem 1: Climbing Stairs
You can take 1 or 2 steps. How many distinct ways to climb to the top?
# This function counts the number of ways to climb n stairs (1 or 2 steps at a time).
def climb_stairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1], dp[2] = 1, 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2] # Ways to reach i = sum of previous two
return dp[n]
# Time complexity: O(n), Space complexity: O(n)
# Example:
n = 4
print(climb_stairs(n)) # Output: 5
🧩 0/1 Knapsack Flow
graph TD
Start[Start: i=1 w=1 to 5]
I1[Item 1: w=1 v=15]
D1[dp1w = max dp0w 15 + dp0w-1]
I2[Item 2: w=3 v=20]
D2[dp2w = max dp1w 20 + dp1w-3]
I3[Item 3: w=4 v=30]
D3[dp3w = max dp2w 30 + dp2w-4]
Result[Result: dp35 = 45]
Start --> I1 --> D1 --> I2 --> D2 --> I3 --> D3 --> Result
style Result fill:#99ff99
🧩 0/1 Knapsack Flow
Suppose weights = [1, 3, 4], values = [15, 20, 30], capacity = 5
i | w | weights[i-1] | weights[i-1] <= w? | dp[i-1][w] | values[i-1] + dp[i-1][w-weights[i-1]] | dp[i][w] |
---|---|---|---|---|---|---|
1 | 1 | 1 | Yes | 0 | 15 + 0 = 15 | 15 |
1 | 2 | 1 | Yes | 0 | 15 + 0 = 15 | 15 |
1 | 3 | 1 | Yes | 0 | 15 + 0 = 15 | 15 |
1 | 4 | 1 | Yes | 0 | 15 + 0 = 15 | 15 |
1 | 5 | 1 | Yes | 0 | 15 + 0 = 15 | 15 |
2 | 1 | 3 | No | 15 | - | 15 |
2 | 2 | 3 | No | 15 | - | 15 |
2 | 3 | 3 | Yes | 15 | 20 + 0 = 20 | 20 |
2 | 4 | 3 | Yes | 15 | 20 + 15 = 35 | 35 |
2 | 5 | 3 | Yes | 15 | 20 + 15 = 35 | 35 |
3 | 1 | 4 | No | 15 | - | 15 |
3 | 2 | 4 | No | 15 | - | 15 |
3 | 3 | 4 | No | 20 | - | 20 |
3 | 4 | 4 | Yes | 35 | 30 + 0 = 30 | 35 |
3 | 5 | 4 | Yes | 35 | 30 + 15 = 45 | 45 |
- Final result: 45 (maximum value).
📘 Sample Problem 2: 0/1 Knapsack
Given weights, values, and a capacity, find max value you can carry.
# This function solves the 0/1 Knapsack problem using DP.
def knapsack(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)] # DP table
for i in range(1, n + 1):
for w in range(capacity + 1):
if weights[i - 1] <= w:
# Max of not taking or taking the item
dp[i][w] = max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]])
else:
dp[i][w] = dp[i - 1][w]
return dp[n][capacity]
# Time complexity: O(n*capacity), Space complexity: O(n*capacity)
# Example:
weights = [1, 3, 4]
values = [15, 20, 30]
capacity = 5
print(knapsack(weights, values, capacity)) # Output: 45
🔁 Variants
- Space optimized (e.g. 1D DP for Fibonacci)
- State compression (e.g. Bitmask DP)
- Tabulation vs memoization
There are currently no comments on this article, be the first to add one below