Knapsack
Choose which items to pack under a weight limit for the most value. Fill the item-by-capacity table, read back the chosen items, then shrink it to one row.
On this page
You sell at a weekend market and carry your goods on a bike trailer that holds 5 kg. You have three things you could bring: a box of honey (2 kg, profit 4), a wheel of cheese (3 kg, profit 7) and a crate of jam (4 kg, profit 10). Each one comes whole or not at all. What should you load?
Jam earns the most per kilo, 2.5, so a common rule takes it first. Then 1 kg is left and nothing fits: profit 10. Honey and cheese together weigh exactly 5 kg and earn 11. With 3 items you could check all 8 combinations. With 40 items there are over a trillion. This is the 0/1 knapsack problem: every item is either in (1) or out (0).
Why one index is not enough
In 1D Dynamic Programming the state was "the best answer for the first i days". Try that here: "the best profit from the first i items". To decide whether to add item i, you need to know how much space the earlier items used, and that number is not in the state. Two packings with equal profit can leave different room.
So put the space into the state too. That gives a table with one row per item and one column per amount of space, the same kind of table you filled in DP on Grids.
The five steps
- State.
best[i][w]is the most profit you can make using only the firstiitems, with at mostwkg of space. - Recurrence. Item
iis either left out or packed. Leave it out and you getbest[i − 1][w]. Pack it, if it fits, and you get its value plus the best you can do with the earlier items in the space that remains:value[i] + best[i − 1][w − weight[i]]. Take the larger. - Base cases.
best[0][w] = 0for everyw: with no items you earn nothing. - Order. Each cell reads only the row above. Fill row by row.
- Answer.
best[n][W]: all items allowed, full capacity.
The "take" arrow always comes from the row above. That is what stops an item being used twice: once you pack the cheese, the rest of the space can only hold items from earlier rows.
best[0][w] = 0 for every wfor i from 1 to n: for w from 0 to W: best[i][w] = best[i-1][w] # skip item i if weight[i] <= w: take = value[i] + best[i-1][w - weight[i]] best[i][w] = max(best[i][w], take)return best[n][W]- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) + cheese (3 kg, 7) + jam (4 kg, 10) - capacity
- 5
1Base case. With no items to choose from, you earn 0 whatever the space. Each row below adds one more item to choose from.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 + cheese (3 kg, 7) + jam (4 kg, 10) - skip
- 0
2Row 1: now honey is allowed too. With 0 kg of space nothing fits, so copy the 0 from above.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 + cheese (3 kg, 7) + jam (4 kg, 10) - skip
- 0
3Honey weighs 2 kg and only 1 kg is free. It cannot go in, so copy the cell above: 0.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 + cheese (3 kg, 7) + jam (4 kg, 10) - skip
- 0
- take
- 4
42 kg free. Skip honey: 0, from above. Take it: 4 +
best[0][0]= 4 + 0 = 4, the best use of the 0 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 + cheese (3 kg, 7) + jam (4 kg, 10) - skip
- 0
- take
- 4
53 kg free. Skip honey: 0, from above. Take it: 4 +
best[0][1]= 4 + 0 = 4, the best use of the 1 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 + cheese (3 kg, 7) + jam (4 kg, 10) - skip
- 0
- take
- 4
64 kg free. Skip honey: 0, from above. Take it: 4 +
best[0][2]= 4 + 0 = 4, the best use of the 2 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) + jam (4 kg, 10) - skip
- 0
- take
- 4
75 kg free. Skip honey: 0, from above. Take it: 4 +
best[0][3]= 4 + 0 = 4, the best use of the 3 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 + jam (4 kg, 10) - skip
- 0
8Row 2: now cheese is allowed too. With 0 kg of space nothing fits, so copy the 0 from above.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 + jam (4 kg, 10) - skip
- 0
9Cheese weighs 3 kg and only 1 kg is free. It cannot go in, so copy the cell above: 0.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 + jam (4 kg, 10) - skip
- 4
10Cheese (3 kg) does not fit in 2 kg. Copy the cell above: 4.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 + jam (4 kg, 10) - skip
- 4
- take
- 7
113 kg free. Skip cheese: 4, from above. Take it: 7 +
best[1][0]= 7 + 0 = 7, the best use of the 0 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 + jam (4 kg, 10) - skip
- 4
- take
- 7
124 kg free. Skip cheese: 4, from above. Take it: 7 +
best[1][1]= 7 + 0 = 7, the best use of the 1 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) - skip
- 4
- take
- 11
135 kg free. Skip cheese: 4, from above. Take it: 7 +
best[1][2]= 7 + 4 = 11, the best use of the 2 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) 0 - skip
- 0
14Row 3: now jam is allowed too. With 0 kg of space nothing fits, so copy the 0 from above.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) 0 0 - skip
- 0
15Jam weighs 4 kg and only 1 kg is free. It cannot go in, so copy the cell above: 0.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) 0 0 4 - skip
- 4
16Jam (4 kg) does not fit in 2 kg. Copy the cell above: 4.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) 0 0 4 7 - skip
- 7
17Jam (4 kg) does not fit in 3 kg. Copy the cell above: 7.
- best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) 0 0 4 7 10 - skip
- 7
- take
- 10
184 kg free. Skip jam: 7, from above. Take it: 10 +
best[2][0]= 10 + 0 = 10, the best use of the 0 kg left over. Taking wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) 0 0 4 7 10 11 - skip
- 11
- take
- 10
195 kg free. Skip jam: 11, from above. Take it: 10 +
best[2][1]= 10 + 0 = 10, the best use of the 1 kg left over. Skipping wins. - best[i][w]
0 kg 1 kg 2 kg 3 kg 4 kg 5 kg no items 0 0 0 0 0 0 + honey (2 kg, 4) 0 0 4 4 4 4 + cheese (3 kg, 7) 0 0 4 7 7 11 + jam (4 kg, 10) 0 0 4 7 10 11 - answer
- 11
- items
- honey + cheese
20The answer is
best[3][5] = 11. Walk back to see which items: 11 matches the cell above, so jam was skipped.best[2][5]took cheese and pointed atbest[1][2], which took honey. Jam earns the most per kilo, but taking it first leaves 1 kg that nothing fits in, for only 10.
The code
function bestProfit(weights, values, capacity) {
const n = weights.length;
const best = Array.from({ length: n + 1 }, () => new Array(capacity + 1).fill(0));
for (let i = 1; i <= n; i++) {
const weight = weights[i - 1]; // row i is item i - 1 in the arrays
const value = values[i - 1];
for (let w = 0; w <= capacity; w++) {
best[i][w] = best[i - 1][w]; // skip the item
if (weight <= w) {
best[i][w] = Math.max(best[i][w], value + best[i - 1][w - weight]); // take it
}
}
}
return best[n][capacity];
}
bestProfit([2, 3, 4], [4, 7, 10], 5); // 11def best_profit(weights, values, capacity):
n = len(weights)
best = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
weight, value = weights[i - 1], values[i - 1] # row i is item i - 1
for w in range(capacity + 1):
best[i][w] = best[i - 1][w] # skip the item
if weight <= w:
best[i][w] = max(best[i][w], value + best[i - 1][w - weight]) # take it
return best[n][capacity]
best_profit([2, 3, 4], [4, 7, 10], 5) # 11One row is enough, if you go backwards
Each row only reads the row above, so you can keep a single array and overwrite it item by item. There is one catch. The "take" option reads best[w − weight], a cell to the left. If you loop w upward, that cell has already been overwritten for the current item, and you end up packing the item twice. Loop w downward and every cell you read on the left still holds the old row.
function bestProfitOneRow(weights, values, capacity) {
const best = new Array(capacity + 1).fill(0);
for (let i = 0; i < weights.length; i++) {
// High to low, so best[w - weights[i]] is still from the previous item.
for (let w = capacity; w >= weights[i]; w--) {
best[w] = Math.max(best[w], values[i] + best[w - weights[i]]);
}
}
return best[capacity];
}def best_profit_one_row(weights, values, capacity):
best = [0] * (capacity + 1)
for weight, value in zip(weights, values):
# High to low, so best[w - weight] is still from the previous item.
for w in range(capacity, weight - 1, -1):
best[w] = max(best[w], value + best[w - weight])
return best[capacity]| Approach | Time | Space | Why |
|---|---|---|---|
| Try every subset | O(2ⁿ × n) | O(n) | Each of n items is in or out, and each subset takes up to n steps to add up. |
| Full table | O(n × W) | O(n × W) | One cell per item and capacity, two options per cell. You can walk back to list the items. |
| One row | O(n × W) | O(W) | Same work. You can no longer walk back to list the items. |
The cost depends on the capacity W as well as on the number of items. Three items and a 5 kg limit is 4 × 6 = 24 cells. Write the same weights in grams and the limit becomes 5,000 g: 4 × 5,001 cells for the same answer. With 20 items and a capacity in the billions the table is hopeless, while trying all 2²⁰ (about a million) subsets is quick.
Common mistakes
Check yourself
Items: (1 kg, profit 3) and (3 kg, profit 5). Capacity 3 kg. What is best[2][3]?
Why does the "take" option read best[i − 1][…] and not best[i][…]?
A packing problem has 30 items and a capacity of 50. Roughly how many cells does the full table have?
What to practise next
The table here had items down the side and space across the top. DP on Strings uses a table with one word down the side and another across the top, and fills it with the same "look at the cells you came from" habit. To practise this lesson, try Busking Set List (each item once) and Vending Machine Coin Mixes (unlimited copies, counting).