Unbounded Knapsack
Maximize value under a capacity when every item may be taken any number of times — the 0/1 loop run forward.
Overview
Same setup as 0/1 Knapsack — weights, values, capacity W — but each item type is available in unlimited supply. The optimal solution may contain the same item many times. Rod cutting (cut a rod of length n into pieces with given prices to maximize revenue) and "minimum coins to make change" (Coin Change) are instances.
The DP is one line different from 0/1: the state loses the "first i items" dimension (or, equivalently, the inner capacity loop runs upward), because after taking an item you are allowed to consider it again.
Intuition
A mental model before the formal terms.
Rod cutting with prices p[1..4] = {1, 5, 8, 9} for lengths 1..4 and a rod of length 4. Options: no cut (9), 1+3 (1+8 = 9), 2+2 (5+5 = 10), 1+1+2 (1+1+5 = 7), four 1s (4). Best is 2+2 = 10 — the same piece used twice, which the 0/1 model forbids.
Think of filling capacity c by choosing the *last* item placed. Whatever it is, the remainder c - w must itself be filled optimally, and that remainder is free to use the same item again. So dp[c] looks at dp[c - w] from the current state of the array, not the previous row.
How it works
- State:
dp[c]= maximum value achievable with total weight ≤cusing any number of copies of each item. - Transition:
dp[c] = max over items i with w[i] ≤ c of dp[c - w[i]] + v[i](anddp[c]itself, for "at most"). The 2D form isdp[i][c] = max(dp[i-1][c], dp[i][c - w[i]] + v[i])— notedp[i], notdp[i-1], in the take branch. - Base case:
dp[0] = 0. For "exactlyc", setdp[c>0] = -∞initially. - Iteration order: item-outer, capacity-inner ascending from
w[i]toW; or capacity-outer, item-inner. Both work becausedp[c - w]is finished beforedp[c]either way. Item-outer is preferred when the number of *combinations* is being counted (it avoids counting orderings). - Answer location:
dp[W]. - Space optimization: already 1D. The
idimension is unnecessary because reuse is allowed, so the "previous row" distinction disappears.
Why it works
Optimal substructure: take any optimal multiset for capacity c and remove one copy of some item i in it. What remains is a multiset of weight ≤ c - w[i], and it must be optimal for that capacity — otherwise replacing it with a better one (plus item i again) would beat the assumed optimum. Hence the max over all possible "last items" is exact.
Ascending iteration is correct precisely because dp[c - w[i]] may already include item i; that is allowed, so reading the updated value is the intended semantics. The 0/1 version needs the descending loop to *prevent* this.
The item-outer ordering computes, for each prefix of items, the best value using only those items with repetition; by induction over items the final array is optimal over all items.
Recognition
How to tell a problem wants this.
- "Unlimited supply", "as many as you want", "any number of times", "infinite coins".
- Cutting/partitioning a length or amount into pieces with given prices or costs.
- Combination counting: "how many ways to make amount
Afrom denominations" — item-outer unbounded knapsack with+=.
Interactive visualization
Play, step, change the input. ← → and space work too.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1dp[0..W] = 02for c in 1 .. W:3 for each item i with w[i] <= c:4 dp[c] = max(dp[c], dp[c-w[i]] + v[i])5return dp[W]Pseudocode
1dp = array[W + 1] filled with 02for i in 0..n-1:3 for c from w[i] up to W:4 dp[c] = max(dp[c], dp[c - w[i]] + v[i])5return dp[W]Implementations
1# dp[c] = best value with total weight <= c, unlimited copies of each item2def unbounded_knapsack(weights: list[int], values: list[int], W: int) -> int:31 · State table4 dp = [0] * (W + 1)52 · Base cases6 dp[0] = 0 # nothing fits in capacity 073 · Transition8 for w, v in zip(weights, values):9 for c in range(w, W + 1): # ASCENDING: dp[c - w] may already include this item10 dp[c] = max(dp[c], dp[c - w] + v)114 · Answer12 return dp[W]13 14 15# Rod cutting: prices[k] is the price of a piece of length k + 116def rod_cutting(prices: list[int], n: int) -> int:17 dp = [0] * (n + 1)18 for length in range(1, n + 1):19 for cut in range(1, min(length, len(prices)) + 1):20 dp[length] = max(dp[length], prices[cut - 1] + dp[length - cut])21 return dp[n]dp = [0] * (W + 1)is the whole state; a flat list of ints has no aliasing issue.- Base case
dp[0] = 0. zip(weights, values)pairs items;range(w, W + 1)ascends so the item can be reused within the same pass.rod_cuttingbounds the cut withmin(length, len(prices))so it never indexes past the price list.
Exact for any magnitude; pure-Python loops are ~50x slower than C++, so W·n around 10^7 is the practical limit.
- For "exactly W" use
float("-inf")as the sentinel; it compares correctly with ints inmax. lru_cacheon a closure overcapis the top-down form; recursion depth is W / min_weight, which can exceed the default limit.- Loop order (items outer vs capacity outer) does not change the result here — unlike counting combinations vs permutations.
- Using
range(W, w - 1, -1)(the 0/1 loop) — items can then be used only once. - Starting at
range(0, W + 1):dp[c - w]with a negative index silently wraps to the end of the list in Python. - Deep recursion in the memoized version for small weights and large W.
- Negative index reads: C++ is undefined behaviour, JS/TS return
undefined(→NaN), Python wraps to the end of the list — all wrong, only Python is silent about it being "valid". - Sentinel for "exactly W":
-Infinityin JS/TS,float("-inf")in Python,LLONG_MIN / 2in C++ (a fullLLONG_MINoverflows when a value is added). - Overflow: C++ needs
long long; JS/TS are exact below 2^53; Python is exact.
Complexity
Pseudo-polynomial in W. No 2D table is ever needed.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Items or denominations with unlimited supply and an integer capacity/amount.
- Rod cutting, integer partition with weights, "min/max/count of ways to make a total from reusable parts".
- Coin change in both forms: minimum coins (min-plus) and number of combinations (sum, item-outer loop).
- Each item has a limited count — use 0/1 Knapsack or bounded knapsack with binary splitting.
- Capacity is enormous relative to
n— the table is infeasible; look for greedy structure (canonical coin systems) or number-theoretic shortcuts. - You need *ordered* sequences (permutations) that sum to a total — swap to capacity-outer, item-inner loop (Combination Sum IV), which is a different count.
Alternatives
Common mistakes
- Iterating capacity downward — that makes it 0/1 knapsack and forbids repetition.
- Mixing up the loop order in counting variants: item-outer counts combinations, capacity-outer counts permutations. They differ (for
{1,2}and amount 3: 2 combinations, 3 permutations). - Initializing "exact amount" variants with 0 instead of
-∞/+∞, which lets impossible capacities contribute.
Interview patterns
- Coin Change II: number of combinations —
dp[a] += dp[a - coin], coins outer loop. - Rod Cutting: maximize revenue over cut lengths.
- Perfect Squares: minimum number of squares summing to
n— unbounded min-plus with items1, 4, 9, …. - Integer Break: maximize product of parts — unbounded knapsack with multiplication.
- Coin ChangeIntermediate