Knapsack Problems
The knapsack family asks how to pack items with weights and values into a limited capacity. This lesson covers 0/1 knapsack (each item once), subset sum / partition (LeetCode 416), space-optimized 1D DP, and links to unbounded knapsack — with C, Java, and Python code and output trace tables.
Overview
Knapsack problems differ by whether each item can be used once, unlimited times, or a bounded count.
| Variant | Reuse items? | Typical goal | LeetCode |
|---|---|---|---|
| 0/1 Knapsack | Each item once | Max value in capacity W | Classic / 1049 |
| Subset sum | Each number once | Can sum to target? | 416 |
| Unbounded | Unlimited copies | Max value or min count | Coin change |
| Bounded | Limited copies | Max value | Multi-knapsack variants |
0/1 Knapsack recurrence (max value):
dp[i][w] = max(
dp[i-1][w], // skip item i
dp[i-1][w - weight[i]] + value[i] // take item i (if fits)
)
1D optimized: loop capacity W downward when processing each item
0/1 Knapsack — Max Value
Problem: Given weights, values, and capacity W, maximize total value without exceeding W. Each item at most once.
State: dp[w] = max value achievable with capacity exactly up to w (using items processed so far).
Complexity: O(n × W) time, O(W) space with 1D array.
0/1 Knapsack Code (C, Java, Python)
Python
def knapsack_01(weights, values, capacity):
"""0/1 knapsack — max value, each item used at most once. O(n × W)."""
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
wt, val = weights[i], values[i]
# Descending w: each item used at most once in 1D DP
for w in range(capacity, wt - 1, -1):
dp[w] = max(dp[w], dp[w - wt] + val)
return dp[capacity]
if __name__ == "__main__":
w, v, cap = [1, 2, 3], [6, 10, 12], 5
print(f"weights={w}, values={v}, capacity={cap} → {knapsack_01(w, v, cap)}")
# items 2+3 (wt 2+3=5, val 10+12=22)
Java
public class Knapsack01 {
/** Max value 0/1 knapsack with 1D space optimization. */
public static int knapsack(int[] weights, int[] values, int capacity) {
int[] dp = new int[capacity + 1];
for (int i = 0; i < weights.length; i++) {
int wt = weights[i], val = values[i];
for (int w = capacity; w >= wt; w--) {
dp[w] = Math.max(dp[w], dp[w - wt] + val);
}
}
return dp[capacity];
}
public static void main(String[] args) {
int[] w = {1, 2, 3}, v = {6, 10, 12};
System.out.println(knapsack(w, v, 5)); // 22
}
}
C
#include <stdio.h>
#define MAX_W 1000
/* 0/1 knapsack — return max value */
int knapsack_01(int weights[], int values[], int n, int capacity) {
int dp[MAX_W + 1] = {0};
int i, w, wt, val;
for (i = 0; i < n; i++) {
wt = weights[i];
val = values[i];
for (w = capacity; w >= wt; w--) {
int candidate = dp[w - wt] + val;
if (candidate > dp[w]) dp[w] = candidate;
}
}
return dp[capacity];
}
int main(void) {
int w[] = {1, 2, 3}, v[] = {6, 10, 12};
printf("%d\n", knapsack_01(w, v, 3, 5)); /* 22 */
return 0;
}
0/1 Knapsack Output & Trace
Program output
| Language | Input | Output | Explanation |
|---|---|---|---|
| Python | w=[1,2,3], v=[6,10,12], cap=5 | 22 | Take items 2 and 3 (weights 2+3) |
| Java | same | 22 | Identical logic |
| C | same | 22 | Printed with printf |
DP trace — capacity 0…5 after all items
| Capacity w | dp[w] (max value) | Best selection | Explanation |
|---|---|---|---|
| 0 | 0 | none | Empty knapsack |
| 1 | 6 | item 1 | After item 1 (wt=1, val=6) |
| 2 | 10 | item 2 | Item 2 alone beats 6+… |
| 3 | 16 | 1+2 | 6+10 from items 1 and 2 |
| 4 | 18 | 1+3 | 6+12 |
| 5 | 22 | 2+3 | 10+12 → final answer |
Subset Sum & Partition
Partition Equal Subset Sum (LeetCode 416): Can array be split into two subsets with equal sum?
Equivalent to: does any subset sum to total / 2?
Recurrence: dp[s] = dp[s] OR dp[s − num] — boolean knapsack, each number once.
Subset Sum Code (C, Java, Python)
Python
def can_partition(nums):
"""LeetCode 416 — partition into two equal-sum subsets."""
total = sum(nums)
if total % 2 != 0:
return False # odd total → impossible
target = total // 2
dp = [False] * (target + 1)
dp[0] = True # sum 0 always reachable (empty subset)
for num in nums:
for s in range(target, num - 1, -1):
dp[s] = dp[s] or dp[s - num]
return dp[target]
if __name__ == "__main__":
print(can_partition([1, 5, 11, 5])) # True (11 | 1+5+5)
print(can_partition([1, 2, 3, 5])) # False (total=11)
Java
public class SubsetSumPartition {
public static boolean canPartition(int[] nums) {
int total = 0;
for (int x : nums) total += x;
if (total % 2 != 0) return false;
int target = total / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums) {
for (int s = target; s >= num; s--) {
dp[s] = dp[s] || dp[s - num];
}
}
return dp[target];
}
public static void main(String[] args) {
System.out.println(canPartition(new int[]{1, 5, 11, 5})); // true
System.out.println(canPartition(new int[]{1, 2, 3, 5})); // false
}
}
C
#include <stdio.h>
#include <stdbool.h>
#define MAX_SUM 10000
bool can_partition(int nums[], int n) {
int total = 0, i, num, s;
for (i = 0; i < n; i++) total += nums[i];
if (total % 2 != 0) return false;
int target = total / 2;
bool dp[MAX_SUM + 1] = {false};
dp[0] = true;
for (i = 0; i < n; i++) {
num = nums[i];
for (s = target; s >= num; s--) {
if (dp[s - num]) dp[s] = true;
}
}
return dp[target];
}
int main(void) {
int a[] = {1, 5, 11, 5}, b[] = {1, 2, 3, 5};
printf("%d\n", can_partition(a, 4)); /* 1 = true */
printf("%d\n", can_partition(b, 4)); /* 0 = false */
return 0;
}
Subset Sum Output & Trace
Program output
| Language | Input | Output | Explanation |
|---|---|---|---|
| Python | [1, 5, 11, 5] | True | Subset {11} and {1,5,5} both sum to 11 |
| Python | [1, 2, 3, 5] | False | Total 11 — no subset sums to 5.5 |
| Java / C | same | true / false (1/0) | Same boolean logic |
DP trace — nums = [1, 5, 11, 5], target = 11
| After processing | Reachable sums (sample) | dp[11]? | Explanation |
|---|---|---|---|
| Init | {0} | False | Only empty subset |
| +1 | {0, 1} | False | Can make sum 1 |
| +5 | {0,1,5,6} | False | Add 5 to prior sums |
| +11 | … includes 11 | True | 11 reached directly |
| +5 | … includes 11 | True | Still reachable → answer True |
Space Optimization
2D knapsack dp[i][w] can compress to 1D dp[w] when items are processed sequentially.
| Variant | Inner loop on w | Why |
|---|---|---|
| 0/1 Knapsack | Descending (W → wt) | Prevent reusing same item in one pass |
| Unbounded | Ascending (wt → W) | Allow multiple uses of same item |
| Subset sum | Descending | Each number once — same as 0/1 |
Knapsack Variants
| Problem | DP type | Time | Link |
|---|---|---|---|
| Target sum / +/- signs | 0/1 count | O(n × sum) | LeetCode 494 |
| Last stone weight II | Partition min diff | O(n × sum) | LeetCode 1049 |
| Coin change min | Unbounded min | O(A × C) | Coin change |
| Profit in job scheduling | 0/1 + sort | O(n log n) | Weighted interval |
More Examples
Example 1: 0/1 knapsack — no item fits together
| weights | values | capacity | Output | Best pick |
|---|---|---|---|---|
| [4, 5, 6] | [10, 12, 15] | 4 | 10 | Only item 1 fits |
Example 2: Partition — odd total
| nums | total | Output | Why |
|---|---|---|---|
| [1, 2, 3] | 6 | True | {1,2} and {3} |
| [2, 2, 3] | 7 | False | Odd total — immediate reject |
Example 3: Complexity
| Problem | Time | Space (optimized) | Type |
|---|---|---|---|
| 0/1 max value | O(nW) | O(W) | Pseudo-polynomial |
| Subset sum | O(n × target) | O(target) | Boolean DP |
| Naive recursion | O(2ⁿ) | O(n) stack | Try all subsets |
Approach Comparison
| Approach | Time | Space | When to use |
|---|---|---|---|
| Brute force subsets | O(2ⁿ) | O(n) | n ≤ 20 only |
| 2D DP table | O(nW) | O(nW) | Whiteboard clarity |
| 1D DP optimized | O(nW) | O(W) | Interview final code |
| Top-down memo | O(nW) | O(nW) + stack | Quick recursive draft |
Real-Life Applications
Knapsack and subset-sum patterns appear whenever you have a fixed capacity and must pick a subset of items—each used at most once—to maximize value or reach an exact target.
| Domain | Real scenario | Knapsack mapping | Variant |
|---|---|---|---|
| Logistics | Loading a truck or cargo plane with weight/volume limits | Items = packages; weight = capacity; value = shipping revenue or priority | 0/1 max value |
| Finance | Portfolio selection under a budget cap | Items = investments; cost = price; value = expected return | 0/1 max value |
| Cloud / DevOps | Packing VMs or containers onto a host with CPU/RAM limits | Items = workloads; weight = resource usage; value = SLA priority | 0/1 or multi-dimensional |
| Project management | Choosing features for a release with fixed dev hours | Items = features; weight = effort; value = business impact | 0/1 max value |
| IT / backup | Selecting files to back up within storage quota | Items = files; weight = size; value = criticality score | 0/1 max value |
| Manufacturing | Cutting raw material into parts with minimal waste | Items = part lengths; capacity = stock length; goal = fit subset | Subset sum / bin packing |
| Load balancing | Splitting workloads evenly across two servers | Can subset A reach exactly half of total load? | Partition / subset sum |
| Recruitment | Forming a team with limited headcount and skill slots | Items = candidates; weight = slots used; value = team score | 0/1 with constraints |
Interview Tips
- Ask: max value, exact sum, count ways, or partition?
- Define
dp[w]ordp[i][w]clearly before coding. - For 0/1, emphasize descending capacity loop in 1D form.
- Subset sum: check odd total first for partition problems.
- State pseudo-polynomial complexity O(n × W).
- Connect unbounded variant to coin change.
- Trace small example: 2 items, capacity 3, on whiteboard.
Key Takeaway
0/1 knapsack maximizes value with each item once — loop capacity downward in 1D DP. Subset sum / partition is the same skeleton with boolean states and target sum/2. Master the loop direction: descending = 0/1, ascending = unbounded.
Next up: Longest increasing subsequence · Coin change problems · DP properties & complexity