Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Coding · Dynamic programming

The bounded knapsack

Lesson · 10 min read · Dynamic programming · Bounded knapsack

Each item comes in a limited number of copies. Choose how many of each to take, or split the copies so the 0/1 method applies.

The problem and the direct recurrence

Now item i has count[i] identical copies. The state is still (i, c), but the decision is how many copies of item i to take, from 0 up to its count and up to what fits:

Recurrence

best(i, c)

  1. max over k = 0, 1, …, min(count[i], c / weight[i]) of
  2. k · value[i] + best(i − 1, c − k · weight[i]).

That costs O(capacity · count) per item. When counts are small it is the simplest correct solution.

Binary splitting: back to 0/1

A count of 13 can be written as bundles of 1, 2, 4 and 6 copies. Every number of copies from 0 to 13 is a sum of some of those bundles, so treating each bundle as a single 0/1 item gives exactly the same choices with 4 items instead of 13. In general a count k becomes about log₂ k bundles: 1, 2, 4, …, and the remainder.

def bounded_knapsack(weights, values, counts, cap):
    best = [0] * (cap + 1)
    for w, v, k in zip(weights, values, counts):
        size = 1
        while k > 0:
            take = min(size, k)                # a bundle of 'take' copies
            bw, bv = take * w, take * v
            for c in range(cap, bw - 1, -1):   # 0/1 update for the bundle
                best[c] = max(best[c], bv + best[c - bw])
            k -= take
            size *= 2
    return best[cap]

Capacity 7; item 1 is 2 kg, value 3, two copies; item 2 is 3 kg, value 4, one copy.

  1. Step 1 / 4
    best
    0001020304050607

    Item 1 (2 kg, value 3, two copies) becomes bundles of 1 and 1; item 2 (3 kg, value 4, one copy) stays one bundle. Start with best = 0 everywhere.

  2. Step 2 / 4
    best
    0001323334353637

    Bundle 1× item 1 (2 kg, value 3), capacities downwards: improves capacities 2, 3, 4, 5, 6, 7.

  3. Step 3 / 4
    best
    0001323364656667

    Bundle 1× item 1 (2 kg, value 3), capacities downwards: improves capacities 4, 5, 6, 7.

  4. Step 4 / 4
    best
    00013243647576107

    Bundle 1× item 2 (3 kg, value 4), capacities downwards: improves capacities 3, 5, 6, 7. best[7] = 10: both copies of item 1 and item 2.

Implementation

The problem at the end of this module, in six languages:

def knapsack_bounded(weights, values, counts, capacity):
    best = [0] * (capacity + 1)
    for w, v, k in zip(weights, values, counts):
        size = 1
        while k > 0:
            take = min(size, k)                    # bundles 1, 2, 4, ..., remainder
            bw, bv = take * w, take * v
            for c in range(capacity, bw - 1, -1):  # 0/1 update for this bundle
                best[c] = max(best[c], bv + best[c - bw])
            k -= take
            size *= 2
    return best[capacity]

The same code is the reference solution to The bounded knapsack, where it is compiled and run against the tests in all six languages.

TimeO(capacity · Σ log count)
SpaceO(capacity)

A monotone-queue technique brings it down to O(n · capacity), but binary splitting is what interviews expect.