Unbounded Knapsack
Given n item types, each with a weight and a value, and a knapsack with a fixed weight capacity, choose items to fill it, where any type may be reused any number of times, to maximize total value without exceeding the capacity.
Do this lesson first: coin changeExample input
item types as (weight, value) = (2, 3), (3, 5), (5, 9); capacity = 6; any type may be taken again
Expected output
10
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 types allowed nothing is carried, so it is 0 at every capacity. Columns 0 and 1 compute and stay 0, since every type here weighs at least 2. Fill row by row and left to right within a row. The take option reads its own row further left, so that cell has to be final first, and a left-to-right sweep is what makes it so.
Cols →
Rows ↓row i: may use the first i types
Cols →capacity c: best value that fits
- 1
function unboundedKnapsack(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 item types, 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][c - w]); - 10
} - 11
} - 12
return dp[n][capacity]; - 13
}
The code, the trap, the variations
- 1
function unboundedKnapsack(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 item types, 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][c - w]); - 10
} - 11
} - 12
return dp[n][capacity]; - 13
}
Where people go wrong
Reading the take option from the row above. That allows each type once and returns zero-one-knapsack's answer: 9 at capacity 6, from the (5, 9) alone, against the true 10 from two (3, 5)s. In the one-row form the same mistake is sweeping capacity downward. Both versions run and return a plausible number, and they agree with the right answer wherever the optimum happens to use each type at most once (capacity 5 here, where both say 9), so a test at the wrong capacity passes.
Allow each item at most once.
That is zero-one-knapsack: the take option reads the row above, dp[i - 1][c - w_i]. State, max and fill order stay.
Cap each type at k copies.
Neither row works alone, since one says once and the other says without limit. Either list each type k times as separate 0/1 items, or split its copies into bundles of 1, 2, 4 and so on plus a last bundle of whatever remains (10 becomes 1, 2, 4 and 3), so about log k bundles make every count from 0 to k and none above it. Each bundle is one 0/1 item with that many times the weight and value, the take option then reads the row above as in zero-one-knapsack, and the max stays.
Count the ways to fill exactly capacity c instead of maximizing value.
That is coin-change-count-ways: the values drop out, a sum replaces the max, and row 0 becomes 1 at capacity 0 and 0 elsewhere. The same-row read stays, because a coin can be used again just as an item type can. The loop swap from the execution panel does not carry over: with a sum, putting capacity outside counts orderings, not combinations.