0-1 Knapsack Problem
Given n items, each with a weight and a value, and a knapsack with a fixed weight capacity, choose a subset of items, taking each item whole or not at all, to maximize total value without exceeding the capacity.
Do this lesson first: coin changeExample input
items as (weight, value) = (1, 1), (3, 4), (4, 5), (5, 7); capacity = 7
Expected output
9
Break it down
Answer each question out loud before you open it. Getting it wrong here is the useful part. A revealed answer you never guessed at teaches you nothing.
Fill the table
The table pauses before each cell you have to supply. Type the value the recurrence gives, and the write animation confirms it.
Press start. The animation stops at every cell YOUR recurrence must fill.
Row 0 is the only base row: with no items allowed the knapsack stays empty, so every cell in row 0 is 0. Column 0 computes, and is 0 all the way down, because every item here weighs at least 1. Fill row by row; both cells a cell reads are in the row above, so they are final before anything reads them. In drive mode only the two-option cells in rows 2 to 4 are gated, because row 1's without-it option is always the empty knapsack's 0, so its comparison is a formality.
Cols →
Rows ↓row i: may use the first i items
Cols →capacity c: best value that fits
- 1
function knapsack(weights, values, capacity) { - 2
const n = weights.length; - 3
const dp = Array.from({ length: n + 1 }, () => new Array(capacity + 1).fill(0)); - 4
// row 0: no items, so value 0 at every capacity - 5
for (let i = 1; i <= n; i++) { - 6
const w = weights[i - 1], v = values[i - 1]; - 7
for (let c = 0; c <= capacity; c++) { - 8
dp[i][c] = dp[i - 1][c]; - 9
if (w <= c) dp[i][c] = Math.max(dp[i][c], v + dp[i - 1][c - w]); - 10
} - 11
} - 12
return dp[n][capacity]; - 13
}
The code, the trap, the variations
- 1
function knapsack(weights, values, capacity) { - 2
const n = weights.length; - 3
const dp = Array.from({ length: n + 1 }, () => new Array(capacity + 1).fill(0)); - 4
// row 0: no items, so value 0 at every capacity - 5
for (let i = 1; i <= n; i++) { - 6
const w = weights[i - 1], v = values[i - 1]; - 7
for (let c = 0; c <= capacity; c++) { - 8
dp[i][c] = dp[i - 1][c]; - 9
if (w <= c) dp[i][c] = Math.max(dp[i][c], v + dp[i - 1][c - w]); - 10
} - 11
} - 12
return dp[n][capacity]; - 13
}
Where people go wrong
Packing greedily by value per unit of weight. (5, 7) has the best ratio, 1.4, so greedy packs it first; (3, 4) and (4, 5) no longer fit beside it, (1, 1) does, and greedy stops at 8 with one unit of capacity unused. The table's 9 comes from (3, 4) and (4, 5), a pair greedy never considers once (5, 7) is in. Greedy by ratio is correct for the fractional knapsack, where items can be cut; with whole items it is not, and that difference is the reason this problem needs a table.
Allow each item to be taken any number of times.
That is unbounded-knapsack. The take option reads the current row, dp[i][c - w_i], because the item is still available after being taken, and the state now allows each item any number of times. Row 0, the max and the row-by-row, left-to-right fill stay.
Require the knapsack to be filled to exactly its capacity.
Only row 0 changes: dp[0][0] = 0 and every other row-0 cell becomes minus infinity, meaning that capacity cannot be filled exactly with no items. The max then never builds on an impossible load, and the recurrence is untouched.
Report which items make up the best load.
The fill is identical. Afterwards, walk back from the answer cell: where a cell equals the one above, the item was not needed; where it differs, the item is in. printing-items-in-zero-one-knapsack teaches that walk.