DSA Notes
Master the classic 0/1 Knapsack problem with 2D DP table construction, include/exclude decisions, solution traceback, and space optimization.
The Problem
You have a knapsack with weight capacity W and n items. Each item has a weight wt[i] and value val[i]. You must choose a subset of items to maximize total value without exceeding the weight capacity. Each item is either taken completely or left (0/1 — no fractions).
Example: Capacity = 7, Items: {(weight=1, value=1), (weight=3, value=4), (weight=4, value=5), (weight=5, value=7)}
Why Greedy Fails
You might think: "Just take items with the best value-per-weight ratio." But that does not always work for 0/1 knapsack:
- Item A: weight=3, value=4 (ratio=1.33)
- Item B: weight=4, value=5 (ratio=1.25)
- Item C: weight=5, value=7 (ratio=1.40)
With capacity=7: Greedy picks C first (best ratio), then can only fit A. Total value=11. But the optimal is A+B: weight=7, value=9... wait, that is less. Let us try: B+A gives 4+5=9. Actually C+A gives 7+4=11, and A+B gives 4+5=9. So greedy was right here. But consider: capacity=7, items (4,5), (3,4), (3,4). Greedy might pick (4,5) and one (3,4)=9. But two (3,4)=8 is worse. The point is greedy can fail in general.
The key issue: 0/1 constraint means you cannot take fractions, so ratio-based greedy gives no guarantees.
The DP Approach
State: dp[i][w] = maximum value achievable using items 0..i-1 with capacity w
Transition: For each item i and capacity w:
- Skip item i:
dp[i][w] = dp[i-1][w] - Take item i (if
wt[i] <= w):dp[i][w] = dp[i-1][w - wt[i]] + val[i] - Choose the better:
dp[i][w] = max(skip, take)
Base case: dp[0][w] = 0 for all w (no items = no value)
Step-by-Step Example
| Items | weight=[1, 3, 4, 5], value=[1, 4, 5, 7], Capacity=7 |
| i=1 0 1 1 1 1 1 1 1 (item | wt=1, val=1) |
| i=2 0 1 1 4 5 5 5 5 (item | wt=3, val=4) |
| i=3 0 1 1 4 5 6 6 9 (item | wt=4, val=5) |
| i=4 0 1 1 4 5 7 8 9 (item | wt=5, val=7) |
| Answer | dp[4][7] = 9 |
| How? At i=3, w=7 | max(dp[2][7], dp[2][7-4]+5) = max(5, dp[2][3]+5) = max(5, 4+5) = 9 |
| This means | take item 3 (val=5) + best from items 0-2 with remaining capacity 3 = item 2 (val=4) |
| Items chosen | item 2 (wt=3,val=4) + item 3 (wt=4,val=5) = weight 7, value 9 ✓ |
Python Implementation
Traceback: Finding Which Items Were Chosen
Space Optimized: O(W) Space
Since dp[i][w] only depends on dp[i-1][...], we can use a single 1D array. The trick: iterate capacity in REVERSE to avoid overwriting values we still need.
Why reverse? If we iterate forward, dp[w - weights[i]] might already be updated in this iteration (meaning we used item i twice). Reverse ensures we read "previous row" values.
C++ Implementation
Time and Space Complexity
- 2D version: Time O(n × W), Space O(n × W)
- 1D optimized: Time O(n × W), Space O(W)
- Note: This is pseudo-polynomial — polynomial in W (the value), not in the input size. If W is exponentially large, this is slow.
Variations
Unbounded Knapsack (items can be reused)
Use forward iteration instead of reverse:
Subset Sum (can we make exact weight W?)
Count of subsets with given sum
Real-World Applications
- Budget allocation: Choose projects maximizing profit within budget
- Cargo loading: Load containers maximizing value within weight limit
- Resource scheduling: Assign resources maximizing utilization
- Portfolio optimization: Select investments maximizing return within risk budget
- Cutting stock: Cut material pieces maximizing usage from limited stock
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for 0/1 Knapsack Problem.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, dynamic, programming, knapsack
Related Data Structures & Algorithms Topics