Expert: advanced patterns and interviews · Dynamic programming · topic 55 of 61 · 40 min
Knapsack problems
Explanation
The problem
You are packing a trekking bag that can carry at most W kilograms. Each item has a weight and a value (how useful it is). Each item is either taken or left: you cannot take half a tent, and you have only one of each. Which items give the largest total value without going over W? This is the 0/1 knapsack problem ("knapsack" is an old word for a backpack; "0/1" means each item is taken 0 or 1 times).
Trying every subset of n items is O(2ⁿ). Greedy by value per kilogram fails too: with capacity 50 and items (weight 10, value 60), (20, 100), (30, 120), the best ratio picks the first two for 160, but the last two give 220.
The 2-D table
State: dp[i][c] = the best value using only the first i items with capacity c. For item i (weight w, value v) there are two choices. Skip it: dp[i − 1][c]. Take it, if w ≤ c: v + dp[i − 1][c − w], the best with the remaining capacity from the earlier items only. dp[i][c] is the larger. Base case: dp[0][c] = 0 (no items, no value). The answer is dp[n][W]. Time and space are O(n × W).
| items \ capacity | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| none | 0 | 0 | 0 | 0 | 0 |
| + (w 1, v 15) | 0 | 15 | 15 | 15 | 15 |
| + (w 3, v 20) | 0 | 15 | 15 | 20 | 35 |
| + (w 4, v 30) | 0 | 15 | 15 | 20 | 35 |
In the table, capacity 4 with the first two items gives 35 (both items, weight 4). The third item alone (value 30) cannot beat that, so the last row stays 35.
One row, looping capacity downwards
Row i only reads row i − 1, so one array dp[c] can hold "the previous row" and be updated in place. The catch is the order. dp[c] reads dp[c − w], a SMALLER capacity. If you loop c upwards, dp[c − w] has already been updated for this item, so the item gets counted again. If you loop c DOWNWARDS from W to w, dp[c − w] still holds the value from before this item, so each item is used at most once. Space drops to O(W).
def knapsack_01(weights, values, capacity):
dp = [0] * (capacity + 1)
for w, v in zip(weights, values):
for c in range(capacity, w - 1, -1): # downwards!
dp[c] = max(dp[c], dp[c - w] + v)
return dp[capacity]
print(knapsack_01([10, 20, 30], [60, 100, 120], 50)) # Output: 220Unbounded knapsack: loop upwards
If you have unlimited copies of each item (like coins in coin change), you WANT the item to be reusable. Loop c upwards: dp[c − w] may already include this item, so dp[c] can add it again. With one item of weight 2 and value 3 and capacity 10, the 0/1 answer is 3 and the unbounded answer is 15. The loop direction is the whole difference.
Subset sum and equal partition
Subset sum asks: can some of the numbers add up exactly to a target T? It is a knapsack where weight equals value and we only care about yes or no. can[s] = True if some subset sums to s; can[0] = True; for each number x, loop s downwards from T to x and set can[s] = can[s] or can[s − x].
Partition equal subset sum asks whether a list can be split into two groups with equal sums. If the total is odd, the answer is no at once. Otherwise it is subset sum with T = total / 2: if one group reaches half, the rest is the other half.
Counting subsets, and target sum
Replace True/False with counts: ways[0] = 1, and ways[s] += ways[s − x] while looping s downwards. This counts subsets by position, so in [1, 1, 2] the two 1s are different items. A zero doubles the count, because each subset can include it or not.
The "target sum" puzzle puts a + or − in front of every number and asks how many sign choices reach a target. Let P be the sum of the plus numbers and N the sum of the minus numbers. P − N = target and P + N = total, so P = (total + target) / 2. If that is not a whole number, or is negative, the answer is 0. Otherwise count the subsets that sum to P.
Common mistakes
- Looping capacity upwards in 0/1 knapsack, so items are reused. Fix: downwards for 0/1, upwards for unbounded.
- Sorting by value per weight and taking greedily. Fix: greedy works only for the fractional knapsack (where you may take part of an item), not for 0/1.
- Forgetting the odd-total check in equal partition. Fix: if total % 2 == 1, return False before building the table.
- Starting the count array with ways[0] = 0. Fix: the empty subset makes sum 0, so ways[0] = 1.
- In target sum, not checking that (total + target) is even and non-negative. Fix: return 0 for those cases before counting.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Knapsack problems · 1/8
This lesson is about knapsack problems: choosing items with weights and values to fit a bag of fixed capacity.
Prefer a video by a teacher? Find free videos on “0/1 knapsack dynamic programming 1d array partition equal subset sum” on YouTube ↗
In real life: Packing a trekking bag
Before a Himalayan trek, a group leader lets each trekker carry at most 6 kg in their day pack. Each item has a weight and a usefulness score agreed by the group. Instead of arguing, the leader runs a small 0/1 knapsack and then walks back through the table to see WHICH items were chosen: a row where the value changed means that item was taken. The same idea, with many more rules, appears in loading delivery bags, choosing which ads fit a time slot, and picking projects within a budget.
items = [("water", 3, 10), ("first-aid kit", 1, 8), ("camera", 2, 4),
("jacket", 2, 7), ("snacks", 1, 5), ("book", 1, 1)]
cap = 6
n = len(items)
dp = [[0] * (cap + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
name, w, v = items[i - 1]
for c in range(cap + 1):
dp[i][c] = dp[i - 1][c]
if w <= c:
dp[i][c] = max(dp[i][c], dp[i - 1][c - w] + v)
chosen, c = [], cap
for i in range(n, 0, -1):
if dp[i][c] != dp[i - 1][c]: # the value changed, so item i was taken
name, w, v = items[i - 1]
chosen.append(name)
c -= w
print(dp[n][cap], chosen[::-1])Practice (0/4 done)
Write knapsack_01(weights, values, capacity). Item i has weight weights[i] > 0 and value values[i] ≥ 0, and each item can be taken at most once. Return the largest total value whose total weight is at most capacity (capacity ≥ 0). Use a 1-D array and loop the capacity downwards. Example: knapsack_01([1, 3, 4, 5], [1, 4, 5, 7], 7) returns 9.
Write can_partition(nums) for a list of non-negative integers. Return True if the list can be split into two groups (every number in exactly one group) with equal sums, else False. An empty list returns True (two empty groups). Example: can_partition([1, 5, 11, 5]) returns True ([1, 5, 5] and [11]).
Write unbounded_knapsack(weights, values, capacity). Same as 0/1 knapsack, but you may take ANY number of copies of each item. Weights are positive. Return the largest total value with total weight at most capacity. Example: unbounded_knapsack([1, 3, 4], [15, 50, 60], 8) returns 130 (two items of weight 3 and two of weight 1).
Write count_subsets_with_sum(nums, target) for non-negative integers nums and target ≥ 0. Return how many subsets (chosen by position, so equal numbers at different positions are different items) have a sum equal to target. The empty subset counts when target is 0. Example: count_subsets_with_sum([1, 1, 2, 3], 4) returns 3 ({1, 3} twice and {1, 1, 2}).
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- More DP patterns and how to spot themDynamic programming · same module
- 1-D DP classicsDynamic programming · same module
- OVER and PARTITION BYSQL and Databases · Window functions
- Bit tricks in interviewsTries, union-find and bit manipulation
- Prefix sums and difference arraysArrays and strings
- Running totals, moving averages and framesSQL and Databases · Window functions
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Why must the 1-D 0/1 knapsack loop capacity downwards?
Answer: So dp[c − w] still holds the value from before this item, and the item is used at most once
Going up, dp[c − w] may already include the current item, which would let it be taken twice.
Capacity 50; items (10 kg, 60), (20 kg, 100), (30 kg, 120). What is the best 0/1 value?
Answer: 220
20 kg + 30 kg = 50 kg for 100 + 120 = 220. Greedy by ratio stops at 160.
nums = [1, 2, 3, 5]. Can it be split into two groups with equal sums?
Answer: No, the total 11 is odd
An odd total can never be split into two equal whole-number halves.
Target sum: numbers [1, 1, 1, 1, 1], target 3, each with + or −. Which subset sum P do you count?
Answer: P = 4
P = (total + target) / 2 = (5 + 3) / 2 = 4. There are 5 ways to choose four 1s.
Why is O(n × W) knapsack called pseudo-polynomial?
Answer: It grows with the VALUE of W, which can be huge, not with the size of the input
A capacity of 10⁹ is a short number to write down but makes a table with a billion columns.
Why does the fractional knapsack greedy fail on the 0/1 knapsack?Related
Answer: Because whole items can leave unused space, so the best value per kilo is not always best overall
With capacity 50, greedy takes 60 + 100 = 160 and cannot fit the 30 kg item; 100 + 120 = 220 is better.
From More greedy classics
How many subsets does a set of 6 distinct items have?Related
Answer: 64
Each item is in or out: 2⁶ = 64, including the empty set.
In the ways-to-make-change DP, putting the AMOUNTS in the outer loop and coins in the inner loop counts…Related
Answer: Ordered sequences, so 1+2 and 2+1 are counted separately
With amounts outside, every coin can be the last one at every amount, so each ordering is a separate path.
From 1-D DP classics
What does SUM(salary) OVER () put on each row?Related
Answer: The total of all salaries in the result
Empty brackets make the window the whole result, so every row shows the same grand total.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Dynamic programming” before its exam: More DP patterns and how to spot them.
- Module 11 exam: Dynamic programming · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Interview strategy and mock problems. 61 topics left in the course before the final exam.