Knapsack

Choose which items to pack under a weight limit for the most value. Fill the item-by-capacity table, read back the chosen items, then shrink it to one row.

18 minLesson 4 of 6
On this page
  1. Why one index is not enough
  2. The five steps
  3. The code
  4. One row is enough, if you go backwards
  5. Common mistakes
  6. Check yourself
  7. What to practise next

You sell at a weekend market and carry your goods on a bike trailer that holds 5 kg. You have three things you could bring: a box of honey (2 kg, profit 4), a wheel of cheese (3 kg, profit 7) and a crate of jam (4 kg, profit 10). Each one comes whole or not at all. What should you load?

Jam earns the most per kilo, 2.5, so a common rule takes it first. Then 1 kg is left and nothing fits: profit 10. Honey and cheese together weigh exactly 5 kg and earn 11. With 3 items you could check all 8 combinations. With 40 items there are over a trillion. This is the 0/1 knapsack problem: every item is either in (1) or out (0).

Why one index is not enough

In 1D Dynamic Programming the state was "the best answer for the first i days". Try that here: "the best profit from the first i items". To decide whether to add item i, you need to know how much space the earlier items used, and that number is not in the state. Two packings with equal profit can leave different room.

So put the space into the state too. That gives a table with one row per item and one column per amount of space, the same kind of table you filled in DP on Grids.

The five steps

  1. State. best[i][w] is the most profit you can make using only the first i items, with at most w kg of space.
  2. Recurrence. Item i is either left out or packed. Leave it out and you get best[i − 1][w]. Pack it, if it fits, and you get its value plus the best you can do with the earlier items in the space that remains: value[i] + best[i − 1][w − weight[i]]. Take the larger.
  3. Base cases. best[0][w] = 0 for every w: with no items you earn nothing.
  4. Order. Each cell reads only the row above. Fill row by row.
  5. Answer. best[n][W]: all items allowed, full capacity.
row i − 1 row i w=0 w=1 w=2 w=3 w=4 w=5 skip take the item's weight (3 kg here)
To fill a cell, look at the row above: straight up if you skip the item, or back by the item's weight if you take it.

The "take" arrow always comes from the row above. That is what stops an item being used twice: once you pack the cheese, the rest of the space can only hold items from earlier rows.

Fill the table for a 5 kg trailerUse the arrows or ← → keys
best[0][w] = 0 for every wfor i from 1 to n:  for w from 0 to W:    best[i][w] = best[i-1][w]            # skip item i    if weight[i] <= w:      take = value[i] + best[i-1][w - weight[i]]      best[i][w] = max(best[i][w], take)return best[n][W]
  1. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)
    + cheese (3 kg, 7)
    + jam (4 kg, 10)
    capacity
    5

    1Base case. With no items to choose from, you earn 0 whatever the space. Each row below adds one more item to choose from.

  2. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)0
    + cheese (3 kg, 7)
    + jam (4 kg, 10)
    skip
    0

    2Row 1: now honey is allowed too. With 0 kg of space nothing fits, so copy the 0 from above.

  3. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)00
    + cheese (3 kg, 7)
    + jam (4 kg, 10)
    skip
    0

    3Honey weighs 2 kg and only 1 kg is free. It cannot go in, so copy the cell above: 0.

  4. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004
    + cheese (3 kg, 7)
    + jam (4 kg, 10)
    skip
    0
    take
    4

    42 kg free. Skip honey: 0, from above. Take it: 4 + best[0][0] = 4 + 0 = 4, the best use of the 0 kg left over. Taking wins.

  5. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)0044
    + cheese (3 kg, 7)
    + jam (4 kg, 10)
    skip
    0
    take
    4

    53 kg free. Skip honey: 0, from above. Take it: 4 + best[0][1] = 4 + 0 = 4, the best use of the 1 kg left over. Taking wins.

  6. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)00444
    + cheese (3 kg, 7)
    + jam (4 kg, 10)
    skip
    0
    take
    4

    64 kg free. Skip honey: 0, from above. Take it: 4 + best[0][2] = 4 + 0 = 4, the best use of the 2 kg left over. Taking wins.

  7. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)
    + jam (4 kg, 10)
    skip
    0
    take
    4

    75 kg free. Skip honey: 0, from above. Take it: 4 + best[0][3] = 4 + 0 = 4, the best use of the 3 kg left over. Taking wins.

  8. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0
    + jam (4 kg, 10)
    skip
    0

    8Row 2: now cheese is allowed too. With 0 kg of space nothing fits, so copy the 0 from above.

  9. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)00
    + jam (4 kg, 10)
    skip
    0

    9Cheese weighs 3 kg and only 1 kg is free. It cannot go in, so copy the cell above: 0.

  10. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)004
    + jam (4 kg, 10)
    skip
    4

    10Cheese (3 kg) does not fit in 2 kg. Copy the cell above: 4.

  11. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047
    + jam (4 kg, 10)
    skip
    4
    take
    7

    113 kg free. Skip cheese: 4, from above. Take it: 7 + best[1][0] = 7 + 0 = 7, the best use of the 0 kg left over. Taking wins.

  12. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)00477
    + jam (4 kg, 10)
    skip
    4
    take
    7

    124 kg free. Skip cheese: 4, from above. Take it: 7 + best[1][1] = 7 + 0 = 7, the best use of the 1 kg left over. Taking wins.

  13. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)
    skip
    4
    take
    11

    135 kg free. Skip cheese: 4, from above. Take it: 7 + best[1][2] = 7 + 4 = 11, the best use of the 2 kg left over. Taking wins.

  14. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)0
    skip
    0

    14Row 3: now jam is allowed too. With 0 kg of space nothing fits, so copy the 0 from above.

  15. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)00
    skip
    0

    15Jam weighs 4 kg and only 1 kg is free. It cannot go in, so copy the cell above: 0.

  16. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)004
    skip
    4

    16Jam (4 kg) does not fit in 2 kg. Copy the cell above: 4.

  17. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)0047
    skip
    7

    17Jam (4 kg) does not fit in 3 kg. Copy the cell above: 7.

  18. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)004710
    skip
    7
    take
    10

    184 kg free. Skip jam: 7, from above. Take it: 10 + best[2][0] = 10 + 0 = 10, the best use of the 0 kg left over. Taking wins.

  19. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)00471011
    skip
    11
    take
    10

    195 kg free. Skip jam: 11, from above. Take it: 10 + best[2][1] = 10 + 0 = 10, the best use of the 1 kg left over. Skipping wins.

  20. best[i][w]
    0 kg1 kg2 kg3 kg4 kg5 kg
    no items000000
    + honey (2 kg, 4)004444
    + cheese (3 kg, 7)0047711
    + jam (4 kg, 10)00471011
    answer
    11
    items
    honey + cheese

    20The answer is best[3][5] = 11. Walk back to see which items: 11 matches the cell above, so jam was skipped. best[2][5] took cheese and pointed at best[1][2], which took honey. Jam earns the most per kilo, but taking it first leaves 1 kg that nothing fits in, for only 10.

1 / 20

The code

Item-by-capacity table
function bestProfit(weights, values, capacity) {
  const n = weights.length;
  const best = Array.from({ length: n + 1 }, () => new Array(capacity + 1).fill(0));
  for (let i = 1; i <= n; i++) {
    const weight = weights[i - 1]; // row i is item i - 1 in the arrays
    const value = values[i - 1];
    for (let w = 0; w <= capacity; w++) {
      best[i][w] = best[i - 1][w]; // skip the item
      if (weight <= w) {
        best[i][w] = Math.max(best[i][w], value + best[i - 1][w - weight]); // take it
      }
    }
  }
  return best[n][capacity];
}
 
bestProfit([2, 3, 4], [4, 7, 10], 5); // 11
def best_profit(weights, values, capacity):
    n = len(weights)
    best = [[0] * (capacity + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        weight, value = weights[i - 1], values[i - 1]  # row i is item i - 1
        for w in range(capacity + 1):
            best[i][w] = best[i - 1][w]  # skip the item
            if weight <= w:
                best[i][w] = max(best[i][w], value + best[i - 1][w - weight])  # take it
    return best[n][capacity]
 
best_profit([2, 3, 4], [4, 7, 10], 5)  # 11

One row is enough, if you go backwards

Each row only reads the row above, so you can keep a single array and overwrite it item by item. There is one catch. The "take" option reads best[w − weight], a cell to the left. If you loop w upward, that cell has already been overwritten for the current item, and you end up packing the item twice. Loop w downward and every cell you read on the left still holds the old row.

One row, filled right to left
function bestProfitOneRow(weights, values, capacity) {
  const best = new Array(capacity + 1).fill(0);
  for (let i = 0; i < weights.length; i++) {
    // High to low, so best[w - weights[i]] is still from the previous item.
    for (let w = capacity; w >= weights[i]; w--) {
      best[w] = Math.max(best[w], values[i] + best[w - weights[i]]);
    }
  }
  return best[capacity];
}
def best_profit_one_row(weights, values, capacity):
    best = [0] * (capacity + 1)
    for weight, value in zip(weights, values):
        # High to low, so best[w - weight] is still from the previous item.
        for w in range(capacity, weight - 1, -1):
            best[w] = max(best[w], value + best[w - weight])
    return best[capacity]
ApproachTimeSpaceWhy
Try every subsetO(2ⁿ × n)O(n)Each of n items is in or out, and each subset takes up to n steps to add up.
Full tableO(n × W)O(n × W)One cell per item and capacity, two options per cell. You can walk back to list the items.
One rowO(n × W)O(W)Same work. You can no longer walk back to list the items.

The cost depends on the capacity W as well as on the number of items. Three items and a 5 kg limit is 4 × 6 = 24 cells. Write the same weights in grams and the limit becomes 5,000 g: 4 × 5,001 cells for the same answer. With 20 items and a capacity in the billions the table is hopeless, while trying all 2²⁰ (about a million) subsets is quick.

Common mistakes

Check yourself

Quick check

Items: (1 kg, profit 3) and (3 kg, profit 5). Capacity 3 kg. What is best[2][3]?

Why does the "take" option read best[i − 1][…] and not best[i][…]?

A packing problem has 30 items and a capacity of 50. Roughly how many cells does the full table have?

What to practise next

The table here had items down the side and space across the top. DP on Strings uses a table with one word down the side and another across the top, and fills it with the same "look at the cells you came from" habit. To practise this lesson, try Busking Set List (each item once) and Vending Machine Coin Mixes (unlimited copies, counting).