Problem: 0/1 Knapsack #
Our final 2D problem introduces selection under constraints: choosing items to maximize value while respecting capacity limits.
Problem Statement #
Given n items, where each item i has:
- Weight:
weights[i] - Value:
values[i]
And given a knapsack with capacity W, select items to:
- Maximize total value
- Without exceeding capacity W
Constraint: Each item can be used at most once (0/1: either take it or leave it).
Example 1:
Example 2:
Why Greedy Fails #
Greedy by value-to-weight ratio:
Greedy doesn't work! We need DP.
Step 1: Define the State #
What should dp[i][w] represent?
Definition: dp[i][w] = maximum value using the first i items with capacity w
Interpretation:
- i: Considering items 0 through i-1 (0-indexed)
- w: Current weight capacity
- Value: Best we can do with these items and this capacity
Answer: dp[n][W] where n = number of items, W = knapsack capacity
Step 2: Identify Base Cases #
dp[0][w] for all w: No Items #
If we have no items, value is 0 regardless of capacity.
dp[0][w] = 0 for all w
dp[i][0] for all i: No Capacity #
If capacity is 0, we can't take any items, so value is 0.
dp[i][0] = 0 for all i
Base case summary:
Entire first row and first column are 0.
Step 3: Find the Recurrence Relation #
For dp[i][w], we're deciding whether to include item i-1 (the i-th item, 0-indexed).
Two options:
Option 1: Don't Take Item i-1 #
If we skip item i-1:
- Value from item i-1: 0
- Best value: Using first i-1 items with capacity w
- Total:
dp[i-1][w]
Option 2: Take Item i-1 #
Can we even take it?
Constraint: weights[i-1] <= w (item must fit)
If we take item i-1:
- Value gained:
values[i-1] - Capacity used:
weights[i-1] - Remaining capacity:
w - weights[i-1] - Best with remaining capacity:
dp[i-1][w - weights[i-1]] - Total:
values[i-1] + dp[i-1][w - weights[i-1]]
Choose the Better Option #
If item fits:
If item doesn't fit (weights[i-1] > w):
Complete Recurrence #
Step 4: Implementation #
Step 5: Trace Through an Example #
Let's trace knapsack([1, 2, 3], [6, 10, 12], 5):
Maximum value is 22: Take items 1 and 2 (weights 2+3=5, values 10+12=22).
Reconstructing the Selection #
Which items were selected?
Backtracking approach:
Trace for our example:
Complexity Analysis #
Time Complexity: O(n × W)
- Two nested loops: n items × W capacity
- Each cell: O(1) work
- Total: O(n × W)
Note: This is pseudo-polynomial because W is not polynomial in the input size (W could be huge!). For small W, very efficient.
Space Complexity: O(n × W)
- DP table of size (n+1) × (W+1)
- Total: O(n × W)
Space Optimization #
Observation: To compute row i, we only need row i-1.
But there's a catch: We read from dp[i-1][w - weights[i-1]], which is to the left of current position.
Solution: Iterate right-to-left in the inner loop!
Why right-to-left?
When computing dp[w], we need dp[w - weights[i]] from the previous iteration.
If we go left-to-right: We might overwrite dp[w - weights[i]] before using it!
If we go right-to-left: We use dp[w - weights[i]] (old value from previous iteration) before updating dp[w].
Space complexity: O(W) instead of O(n × W)!
0/1 vs Unbounded Knapsack #
Key difference:
0/1 Knapsack (today's problem):
- Each item can be used at most once
- Recurrence:
dp[i][w]depends ondp[i-1][...](previous items only)
Unbounded Knapsack (like Coin Change from Day 39):
- Each item can be used unlimited times
- Recurrence:
dp[i][w]depends ondp[i][...](can reuse same item)
Example of unbounded:
Direction: Left-to-right for unbounded (reuse item), right-to-left for 0/1 (use once).
Variations #
Variation 1: Subset Sum #
Problem: Can we select items with total weight exactly equal to target?
Example:
- nums = [3, 34, 4, 12, 5, 2]
- target = 9
Answer: Yes (4 + 5 = 9)
DP approach:
This is 0/1 knapsack with:
- weights = values = nums
- Check if we can achieve exactly target weight
- Return boolean instead of max value
Variation 2: Partition Equal Subset Sum #
Problem: Can we partition array into two subsets with equal sum?
Example: [1, 5, 11, 5] → Yes: {1, 5, 5} and {11}
Solution: Use subset sum!
If total sum is odd: Impossible (can't split evenly)
If total sum is even: Check if we can make sum/2
This is the same knapsack pattern!
Variation 3: Target Sum #
Problem: Assign +/- to each number to reach target sum.
Example: nums = [1, 1, 1, 1, 1], target = 3
Answer: 5 ways (-1+1+1+1+1, 1-1+1+1+1, etc.)
Transformation:
- Let P = subset assigned +
- Let N = subset assigned -
- P - N = target
- P + N = sum
- Solving: P = (target + sum) / 2
Problem becomes: Count subsets with sum P (0/1 knapsack counting variant!)
Key Takeaways #
0/1 Knapsack demonstrates:
- Selection under constraints: Choose items to optimize value within capacity
- Binary choice: Take or leave each item
- Bounded usage: Each item used at most once
- Pseudo-polynomial time: Efficient for small capacities
Pattern Recognition:
This is a constrained selection problem where:
- State is (items considered, remaining capacity)
- Recurrence is binary choice (take or skip)
- Base cases are no items or no capacity
This pattern appears in:
- Subset Sum
- Partition Equal Subset Sum
- Target Sum
- Coin Change variants
- Job scheduling with dependencies
Knapsack is one of the most versatile DP patterns, with countless real-world applications.
Real-World Applications #
Knapsack appears in:
- Resource allocation: Limited budget, maximize return
- Cargo loading: Limited weight, maximize value
- Project selection: Limited time/money, maximize profit
- Data compression: Limited space, maximize important data
- Portfolio optimization: Limited capital, maximize returns
Summary: Day 40 Complete! #
We've mastered four 2D DP problems:
- Unique Paths: Grid traversal (additive recurrence)
- Longest Common Subsequence: Sequence comparison (conditional recurrence)
- Edit Distance: Sequence transformation (three-way choice)
- 0/1 Knapsack: Constrained selection (binary choice)
Each represents a fundamental 2D pattern you'll see again and again.
Key skills learned:
- Setting up 2D DP tables correctly
- Identifying when problems need 2D state
- Formulating recurrences with multiple parameters
- Space optimization for 2D problems
- Reconstructing solutions from DP tables
The principles from 1D (Day 39) still apply: Define state, identify base cases, find recurrence, implement systematically.
2D DP just adds another dimension of complexity - but the thinking process remains the same!