Pattern #71
0/1 Knapsack
Single-use item selection under capacity constraints. Understand why 1D DP capacity loops MUST run backwards (Right-to-Left) to prevent item reuse across Subset Sum, Target Sum, and Partition problems.
Must Solve
15 core questions β solve these first.
- 1.0/1 Knapsack1D DP right-to-left loop: dp[w] = max(dp[w], val + dp[w-wt])medium
- 2.Partition Equal Subset SumTarget = total/2; 0/1 right-to-left boolean DPmedium
- 3.Subset Sum0/1 reachability: dp[s] = dp[s] || dp[s - num] updated backwardsmedium
- 4.Target SumTransform P = (total + target)/2; count 0/1 subsets with sum Pmedium
- 5.Last Stone Weight IIMin subset sum diff: max subset sum S1 <= total/2; ans = total - 2*S1medium
- 6.Minimum Subset Sum Difference0/1 subset reachability; find largest S1 <= total/2medium
- 7.Count Subsets With Given Sum0/1 counting: dp[s] += dp[s - num] updated backwardsmedium
- 8.Number of Subsets With Given DifferenceCount subsets for sum S1 = (total + diff)/2medium
- 9.Ones and Zeroes2D Capacity 0/1 DP: dp[z][o] updated backwards for each stringmedium
- 10.Tallest BillboardState difference 0/1 DP: dp[diff] tracking max lower heighthard
- 11.Profitable SchemesMulti-dimensional 0/1 DP: members limit + min profit targethard
- 12.Find Target Sum WaysSubset sum counting with zeroes explicit handlingmedium
- 13.Equal Sum PartitionPartition Equal Subset Sum boolean 0/1 mappingmedium
- 14.Knapsack With Exact CapacityInitialize dp[0] = 0, other capacities to -INF to enforce exact fillmedium
- 15.Return Selected Items in KnapsackBacktrack 2D matrix: compare dp[i][w] == dp[i-1][w] to identify chosen itemsmedium
Also Important
5 advanced 0/1 Knapsack variations worth practicing.
- 16.Multi-Dimensional 0/1 KnapsackMultiple capacity dimensions (e.g. weight + volume) updated backwardshard
- 17.Value-Based Knapsack DPSwap state: dp[val] = min weight when total capacity W is hugehard
- 18.Bitset Subset Sum OptimizationBitwise shift: dp |= (dp << num) for 64x subset sum accelerationhard
- 19.Meet-in-the-Middle Subset SumSplit N into N/2 parts + binary search when N <= 40 and W is hugehard
- 20.Group Knapsack β advancedChoose at most 1 item per group: outer group loop, inner capacity right-to-lefthard
How to Think
Each item is used ONCE. When compressing to 1D DP, iterate capacity backwards (Right -> Left).
- Each item usable only once?0/1 Knapsack
- Main decision for each item?TAKE (once) or SKIP
- Limited capacity / target?dp[item][capacity]
- Using 1D DP array?Capacity loop RIGHT -> LEFT
- Need split array into 2 equal parts?Target = total / 2
Go Quick Reference β 0/1 Knapsack
Compilable Go code demonstrating 2D matrix 0/1 Knapsack and Space-Optimized 1D Right-to-Left capacity loop.
package main
import "fmt"
// 0/1 Knapsack 2D Matrix DP: O(N * W) space
func knapsack2D(weights []int, values []int, capacity int) int {
n := len(weights)
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, capacity+1)
}
for i := 1; i <= n; i++ {
wt, val := weights[i-1], values[i-1]
for w := 0; w <= capacity; w++ {
dp[i][w] = dp[i-1][w] // SKIP item i
if wt <= w {
take := val + dp[i-1][w-wt] // TAKE item i (reads previous row i-1)
if take > dp[i][w] {
dp[i][w] = take
}
}
}
}
return dp[n][capacity]
}
// 0/1 Knapsack Space-Optimized 1D DP: O(W) space
func knapsack1D(weights []int, values []int, capacity int) int {
dp := make([]int, capacity+1)
for i := 0; i < len(weights); i++ {
wt, val := weights[i], values[i]
// RIGHT-TO-LEFT LOOP: prevents using current item i twice!
for w := capacity; w >= wt; w-- {
if val+dp[w-wt] > dp[w] {
dp[w] = val + dp[w-wt]
}
}
}
return dp[capacity]
}
func main() {
weights := []int{2, 3, 4}
values := []int{6, 10, 12}
cap := 5
fmt.Println("2D Max Value:", knapsack2D(weights, values, cap)) // Output: 16
fmt.Println("1D Max Value:", knapsack1D(weights, values, cap)) // Output: 16
}π 0/1 Knapsack: O(N Γ W) time and O(W) space with Right-to-Left loop.
Core 0/1 Mechanics & Backward Updating
Why 2D Reads Previous Row dp[i-1]
β’ In 2D table dp[i][w] = max(dp[i-1][w], val + dp[i-1][w-wt]), taking item i reads row i-1.
β’ Since row i-1 did not have item i available, item i can only be taken once!
Why 1D Array Loop Runs Right-to-Left
β’ When iterating backwards (w = W down to wt), dp[w-wt] contains the UNTOUCHED value from the previous item.
β’ Iterating forwards would overwrite dp[w-wt] with the current item, causing item reuse!
0/1 Definition -> Use Each Item AT MOST ONCE
2D Matrix -> dp[i][w] reads dp[i-1][w - weight] (Previous Row)
1D Array -> Loop capacity w = W down to weight (Right-to-Left / Backwards)
Partition Equal Subset Sum -> Target = total / 2 (0/1 Subset Sum)
Last Stone Weight II / Min Diff -> Target = largest S1 <= total / 2; ans = total - 2*S1
Target Sum -> Target Positive Sum P = (total + target) / 20/1 Knapsack Variants & Advanced Optimizations
0/1 Subset Sum Transformations
β’ Subset Sum: Boolean dp[s] = dp[s] || dp[s - num] (Right-to-Left).
β’ Partition Equal Subset: Check if total is even, then solve Subset Sum for target sum / 2.
β’ Target Sum: Transform positive subset count P = (total + target) / 2.
Value-Based & Bitset Optimizations
β’ Value-Based DP: When capacity W is huge (e.g. 10βΉ) but values are small: swap state to dp[val] = min weight.
β’ Bitset Optimization: For boolean subset sum, use bitwise shift dp |= (dp << num) for 64x speedup.
Reconstructing Selected Items
To recover chosen items from 2D DP matrix dp[N][W]:
Backtrack from i = N, w = W:
β’ If dp[i][w] == dp[i-1][w]: Item i was SKIPPED.
β’ Else: Item i was TAKEN -> record item i and set w -= weight[i-1].
Common Interview Mistakes
- β’ Iterating 1D capacity loop forwards (Left-to-Right), accidentally reusing the same item multiple times.
- β’ Reading current row dp[i][w - weight] in 2D take transition instead of previous row dp[i-1][w - weight].
- β’ Updating DP values when item weight > current capacity w (item does not fit in capacity w).
- β’ Using 0 base initialization for counting/boolean problems instead of dp[0] = 1 or dp[0] = true.
- β’ Failing to handle zeroes in Target Sum / Subset counting (zeroes double the valid subset count choices).
- β’ Assuming 0/1 Knapsack is polynomial in N (it is pseudo-polynomial in capacity W).
- β’ Attempting 1D capacity DP when W = 10^9 without checking constraint bounds (causes Memory Limit Exceeded).
- β’ Failing to check odd total sum in Partition Equal Subset Sum (odd sum can never be divided by 2).
- β’ Confusing 0/1 Knapsack (single use per item) with Unbounded Knapsack (unlimited item reuse).
Interview Rules
Single-use item selection + capacity? β 0/1 Knapsack DP
Rule 1 β Define dp[i][w] state meaning explicitly
Rule 2 β 2D Matrix: dp[i][w] = max(dp[i-1][w], val + dp[i-1][w-wt])
Rule 3 β 1D Array: Loop capacity w = W down to weight (Right-to-Left)
Rule 4 β Partition Equal Subset: Target = total / 2
Rule 5 β Target Sum: Target positive subset sum P = (total + target) / 2
Rule 6 β Massive W = 10^9: Use Value-based DP or Meet-in-middle
Time Complexity β O(N * Capacity) (Pseudo-Polynomial)
Space Complexity β O(N * Capacity) 2D Table or O(Capacity) 1D Arrayπ‘ Golden Rule: Every item gets one decision onlyβtake it once or skip itβso when you compress to 1D DP, update capacity backwards to prevent using the same item again.
Production Thinking
Budget Allocation Decisions β 0/1 Knapsack models non-divisible project selections under fixed capital budgets.
Single-Use Job Scheduling β 0/1 Knapsack selects maximum-value discrete jobs under server CPU capacity limits.
Storage Object Selection β disk space optimization selects highest-priority discrete files under fixed byte storage caps.
Production warning β capacity W scaling directly impacts table memory; for massive capacity limits (W > 10βΆ), switch to Value-based DP or Meet-in-the-Middle.
Remember This
0/1 Rule: Each item usable AT MOST ONCE
1D Array: Loop Capacity RIGHT-TO-LEFT (w = W down to wt)
2D Matrix: Take reads Previous Row dp[i-1][w-wt]
Partition Equal Subset: Target = total / 2
Target Sum: Target sum P = (total + target) / 2
Pseudo-Polynomial: Time = O(N * W), Space = O(W)