The unbounded knapsack
There are n item types; type i weighs weights[i] and is worth
values[i], and you may take any number of each. Return the largest total value whose total weight is at most
capacity.
Examples
| Input | Output | Why |
|---|---|---|
weights = [2,3,5], values = [3,5,9], capacity = 9 | 15 | 5 + 2 + 2, or 3 + 3 + 3. |
weights = [1,3,4], values = [15,50,60], capacity = 8 | 130 | 3 + 3 + 1 + 1: 50 + 50 + 15 + 15. Eight of the first item would give only 120. |
weights = [3], values = [5], capacity = 2 | 0 | Nothing fits. |
Constraints
- 1 ≤ n ≤ 100
- 1 ≤ weights[i] ≤ 1000
- 1 ≤ values[i] ≤ 10⁴
- 0 ≤ capacity ≤ 10⁴
Hints
Hint 1
With unlimited copies the item index does not need to be in the state: best[c] = max over items of values[i] + best[c − weights[i]].
Hint 2
Equivalently, run the 0/1 loop with capacities going upwards, so best[c − w] may already include the same item.
Where people lose marks
- Looping downwards, which allows each item only once.
- Assuming the bag must be filled exactly: here it may be left partly empty.
Dry run on [2,3,5],[3,5,9],9
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 10best00·1·2·3·4·5·6·7·8·9
Base case: best[0] = 0.
-
Step 2 / 10best0001·2·3·4·5·6·7·8·9
best[1] = 0.
-
Step 3 / 10best000132·3·4·5·6·7·8·9
best[2] = 3.
-
Step 4 / 10best00013253·4·5·6·7·8·9
best[3] = 5.
-
Step 5 / 10best0001325364·5·6·7·8·9
best[4] = 6.
-
Step 6 / 10best000132536495·6·7·8·9
best[5] = 9.
-
Step 7 / 10best000132536495106·7·8·9
best[6] = 10.
-
Step 8 / 10best000132536495106127·8·9
best[7] = 12.
-
Step 9 / 10best000132536495106127148·9
best[8] = 14.
-
Step 10 / 10best000132536495106127148159
best[9] = 15. The answer is best[9] = 15.
How to think about it
Because any item can be taken again, the best value for capacity c depends only on c:
best[c] = max(0, max over i with weights[i] ≤ c of values[i] + best[c − weights[i]]), filled for c = 0 upwards.
The equivalent loop form keeps items on the outside and capacities going upwards: that direction lets the update for
capacity c read a value that already used the current item.
Complexity. Time O(n · capacity) · Space O(capacity)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def knapsack_unbounded(weights, values, capacity):
best = [0] * (capacity + 1)
for w, v in zip(weights, values):
for c in range(w, capacity + 1): # upwards: the item may be reused
best[c] = max(best[c], v + best[c - w])
return best[capacity]#include <algorithm>
#include <vector>
long long solve(const std::vector<int>& weights, const std::vector<int>& values, int capacity) {
std::vector<long long> best(capacity + 1, 0);
for (size_t i = 0; i < weights.size(); ++i)
for (int c = weights[i]; c <= capacity; ++c) // upwards: reuse allowed
best[c] = std::max(best[c], values[i] + best[c - weights[i]]);
return best[capacity];
}class Solution {
public long knapsackUnbounded(int[] weights, int[] values, int capacity) {
long[] best = new long[capacity + 1];
for (int i = 0; i < weights.length; i++)
for (int c = weights[i]; c <= capacity; c++) // upwards: reuse allowed
best[c] = Math.max(best[c], values[i] + best[c - weights[i]]);
return best[capacity];
}
}function knapsackUnbounded(weights, values, capacity) {
const best = new Array(capacity + 1).fill(0);
for (let i = 0; i < weights.length; i++)
for (let c = weights[i]; c <= capacity; c++) // upwards: reuse allowed
best[c] = Math.max(best[c], values[i] + best[c - weights[i]]);
return best[capacity];
}fn solve(weights: &[i64], values: &[i64], capacity: i64) -> i64 {
let cap = capacity as usize;
let mut best = vec![0i64; cap + 1];
for (&w, &v) in weights.iter().zip(values) {
for c in (w as usize)..=cap { // upwards: reuse allowed
best[c] = best[c].max(v + best[c - w as usize]);
}
}
best[cap]
}import Data.Array
solve :: [Int] -> [Int] -> Int -> Int
solve weights values capacity = best ! capacity
where
items = zip weights values
-- best ! c depends only on smaller capacities, so a lazy array computes it in the right order
best = listArray (0, capacity) [maximum (0 : [v + best ! (c - w) | (w, v) <- items, w <= c]) | c <- [0 .. capacity]] :: Array Int Int
Variants to try
- Rod cutting: pieces of length i sell for price[i]; maximise revenue from a rod of length n.
- Fewest coins for an amount: the same recurrence with min and exact filling.
- Return the multiset of items chosen.
Next problems
The cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem → Medium15 minCount the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem →Write a solution and run it against the real test table.
Keep going
The cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem → Medium15 minCount the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.