{"id":"dsa-dp-knapsack","slug":"knapsack","title":"Challenge: the 0/1 knapsack","text":"A delivery drone can lift only so many kilos. Each parcel waiting at the depot has a weight and a fee for delivering it, and a parcel either flies **whole or stays behind**: you can't take half of one, or the same one twice. That \"all or nothing\" is the \"0/1\" in the name. Which parcels should go up to earn the most without overloading the drone? This is the **0/1 knapsack** (a knapsack is a backpack), and it's the best-known DP problem in interviews.\n\nGreedy fails again. Take parcels of 2, 3, 5 and 6 kilos, with fees of 3, 6, 8 and 9, and a drone that lifts 7 kilos. The biggest fee first takes the 6 kilos (fee 9) and then has room for nothing else: 9. The best fee per kilo first takes the 3 kilos (2 per kilo), then the 2 kilos: 9 again. But 2 + 5 kilos make exactly 7 and earn 3 + 8 = 11. Trying every choice of parcels finds that, but n parcels can be chosen in 2ⁿ ways.\n\n### The table\n\n`best[i][c]` holds the most you can earn with the first `i` items and a capacity of `c`. Item `i` sits at index `i - 1`, with weight `w` and value `v`, and as in the backtracking module there are two choices for it:\n\n- **Leave it:** then everything comes from the earlier items, `best[i - 1][c]`.\n- **Take it**, if it fits (`w <= c`): it earns `v`, and the earlier items fill the `c - w` that's left as well as they can, `v + best[i - 1][c - w]`.\n\n`best[i][c]` is the bigger of the two, and with no items everything is 0:\n\n```python\ndef knapsack_table(weights, values, capacity):\n    n = len(weights)\n    best = [[0] * (capacity + 1) for _ in range(n + 1)]\n    for i in range(1, n + 1):\n        w, v = weights[i - 1], values[i - 1]\n        for c in range(capacity + 1):\n            best[i][c] = best[i - 1][c]                  # leave item i\n            if w <= c and v + best[i - 1][c - w] > best[i][c]:\n                best[i][c] = v + best[i - 1][c - w]      # take item i\n    return best\n\n\nfor row in knapsack_table([2, 3, 5, 6], [3, 6, 8, 9], 7):\n    print(row)\n```\n\n```output\n[0, 0, 0, 0, 0, 0, 0, 0]\n[0, 0, 3, 3, 3, 3, 3, 3]\n[0, 0, 3, 6, 6, 9, 9, 9]\n[0, 0, 3, 6, 6, 9, 9, 11]\n[0, 0, 3, 6, 6, 9, 9, 11]\n```\n\nThe answer is the bottom-right entry, 11. To find **which** items, walk back up from it: where `best[i][c]` differs from the entry just above it, item `i` was taken, so take its weight off `c`; either way, go up a row. Here row 3's 11 differs from the 9 above it (the 5 kilos fly, leaving a capacity of 2), and in that column row 1's 3 differs from the 0 above it (the 2 kilos fly). The table has (n + 1) × (capacity + 1) entries, each O(1): O(n × capacity) time and memory.\n\n### One row is enough\n\nA row of the table is built from the row just above it and nothing else. So one list can stand in for the whole table, overwritten item after item, as long as the inner loop runs through the capacities **from the top down**:\n\n```python\nbest = [0] * (capacity + 1)\nfor w, v in zip(weights, values):\n    for c in range(capacity, w - 1, -1):\n        best[c] = max(best[c], v + best[c - w])\n```\n\n**`zip(weights, values)`** walks through the two lists side by side, giving the pairs `(weights[0], values[0])`, `(weights[1], values[1])` and so on, and `for w, v in` unpacks each pair into two variables.\n\nWhy downward? `c - w` is smaller than `c`, so on the way down the loop reaches `best[c - w]` only **after** `best[c]`. So `best[c - w]` is still untouched by this item: it holds what the earlier items alone can earn, as the row above would. Going **up** is the classic mistake. Then `best[c - w]` may already include this item, so the item is taken again and again, like a coin in the coin change step:\n\n```python\ndef one_row(weights, values, capacity, downward):\n    best = [0] * (capacity + 1)\n    for w, v in zip(weights, values):\n        if downward:\n            capacities = range(capacity, w - 1, -1)\n        else:\n            capacities = range(w, capacity + 1)\n        for c in capacities:\n            best[c] = max(best[c], v + best[c - w])\n    return best[capacity]\n\n\nprint(one_row([3], [8], 9, True), one_row([3], [8], 9, False))\n```\n\n```output\n8 24\n```\n\nOne item of weight 3 and a capacity of 9: going down packs it once, going up three times. The one row needs only O(capacity) memory, for the same O(n × capacity) time, but it can't tell you which items were taken: that needs the whole table.\n\n### Which sums can you make?\n\nThe same shape answers \"can some of these numbers add up to exactly t?\", and in Python a **set of the sums you can make** says it directly. Start with `{0}` (take nothing), and for each number, add it to every sum so far:\n\n```python\nsums = {0}\nfor x in [3, 5, 9]:\n    sums |= {s + x for s in sums}\nprint(sorted(sums))\n```\n\n```output\n[0, 3, 5, 8, 9, 12, 14, 17]\n```\n\n`a | b` makes the **union** of two sets: a set of everything that's in `a`, in `b` or in both. `sums |= new` puts every item of `new` into `sums`, the way `+=` adds to a number. Python builds the whole right side first, so the comprehension adds `x` to the sums as they were **before** this number, and no sum gets `x` twice: each number is used at most once, the set's way of going downward. Keep only the sums up to t (`if s + x <= t`), and the set never holds more than t + 1 sums: O(n × t) time and O(t) memory, instead of 2ⁿ subsets.\n\n### When the table is too big\n\nO(n × capacity) grows with the capacity's **size**, not with its number of digits: that's called **pseudo-polynomial** time. For ten items and a capacity of 10 billion, simply trying the 2¹⁰ = 1,024 subsets is far faster, so DP pays when the capacity or the target is modest, as in most interview problems.\n","task":"`count_subsets(numbers, target)` should count the subsets of the positive `numbers` that add up to exactly `target`, each number used at most once. It keeps one row, `ways[s]`, the number of ways to make each sum, as the coin change step did. But it counts too many: `count_subsets([3, 1, 4, 2, 5], 7)` returns 13. Fix it, so that it returns 3 (3 + 4, 2 + 5 and 1 + 4 + 2):\n\n```\n3\n```\n","kind":"code","seed":"def count_subsets(numbers, target):\n    ways = [1] + [0] * target\n    for x in numbers:\n        for s in range(x, target + 1):\n            ways[s] += ways[s - x]\n    return ways[target]\n\n\nif __name__ == \"__main__\":\n    print(count_subsets([3, 1, 4, 2, 5], 7))\n","solution":"def count_subsets(numbers, target):\n    ways = [1] + [0] * target\n    for x in numbers:\n        for s in range(target, x - 1, -1):\n            ways[s] += ways[s - x]\n    return ways[target]\n\n\nif __name__ == \"__main__\":\n    print(count_subsets([3, 1, 4, 2, 5], 7))\n","hints":["Which subsets does it count for `count_subsets([2], 4)`? It answers 1, but the only number is 2, and 4 needs it twice.","Going up through the sums, `ways[s - x]` may already count ways that use `x`, so `x` gets used again: that's the coin change, with unlimited coins.","Loop the sums downward, from `target` to `x`: `range(target, x - 1, -1)`."],"tests":[{"name":"count_subsets([3, 1, 4, 2, 5], 7)","stdin":"","call":"count_subsets([3, 1, 4, 2, 5], 7)\n","expect":"3","hidden":false,"returns":true},{"name":"count_subsets([2], 4)","stdin":"","call":"count_subsets([2], 4)\n","expect":"0","hidden":true,"returns":true},{"name":"count_subsets([5, 5], 5)","stdin":"","call":"count_subsets([5, 5], 5)\n","expect":"2","hidden":true,"returns":true},{"name":"count_subsets([1, 2, 3], 0)","stdin":"","call":"count_subsets([1, 2, 3], 0)\n","expect":"1","hidden":true,"returns":true},{"name":"count_subsets([4, 6], 5)","stdin":"","call":"count_subsets([4, 6], 5)\n","expect":"0","hidden":true,"returns":true},{"name":"count_subsets([1] * 10, 5)","stdin":"","call":"count_subsets([1] * 10, 5)\n","expect":"252","hidden":true,"returns":true},{"name":"count_subsets(list(range(1, 31)), 100)","stdin":"","call":"count_subsets(list(range(1, 31)), 100)\n","expect":"198732","hidden":true,"returns":true}],"require":[],"forbid":[],"more":[{"task":"Write `knapsack(weights, values, capacity)`: the most total value of items whose weights add up to at most `capacity`, each item taken at most once. `weights[i]` and `values[i]` belong to item i, and all of them are positive whole numbers. The tests have 200 items and a capacity of 1,000, where trying every subset never ends, so fill a table, or one row going down.\n\nFor example, `knapsack([2, 3, 5, 6], [3, 6, 8, 9], 7)` returns 11:\n\n```\n11\n```\n","kind":"code","seed":"def knapsack(weights, values, capacity):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(knapsack([2, 3, 5, 6], [3, 6, 8, 9], 7))\n","solution":"def knapsack(weights, values, capacity):\n    best = [0] * (capacity + 1)\n    for w, v in zip(weights, values):\n        for c in range(capacity, w - 1, -1):\n            if v + best[c - w] > best[c]:\n                best[c] = v + best[c - w]\n    return best[capacity]\n\n\nif __name__ == \"__main__\":\n    print(knapsack([2, 3, 5, 6], [3, 6, 8, 9], 7))\n","hints":["The table way: `best` with `len(weights) + 1` rows of `capacity + 1` zeros. Row `i` leaves item `i` (copy the entry above) or takes it when it fits (its value plus the entry above, `w` to the left), whichever is bigger.","The one-row way: `best = [0] * (capacity + 1)`, and for each item, loop `c` from `capacity` down to its weight, keeping the bigger of `best[c]` and `v + best[c - w]`.","Going down is what keeps each item to one use. Going up, the 3-kilo parcel could fly twice, and the first test would give 12 instead of 11."],"tests":[{"name":"knapsack([2, 3, 5, 6], [3, 6, 8, 9], 7)","stdin":"","call":"knapsack([2, 3, 5, 6], [3, 6, 8, 9], 7)\n","expect":"11","hidden":false,"returns":true},{"name":"knapsack([3], [8], 9)","stdin":"","call":"knapsack([3], [8], 9)\n","expect":"8","hidden":true,"returns":true},{"name":"knapsack([7, 9], [5, 8], 6)","stdin":"","call":"knapsack([7, 9], [5, 8], 6)\n","expect":"0","hidden":true,"returns":true},{"name":"knapsack([], [], 10)","stdin":"","call":"knapsack([], [], 10)\n","expect":"0","hidden":true,"returns":true},{"name":"knapsack([3, 4, 2], [30, 50, 15], 6)","stdin":"","call":"knapsack([3, 4, 2], [30, 50, 15], 6)\n","expect":"65","hidden":true,"returns":true},{"name":"knapsack([12, 2, 1, 1, 4], [4, 2, 1, 2, 10], 15)","stdin":"","call":"knapsack([12, 2, 1, 1, 4], [4, 2, 1, 2, 10], 15)\n","expect":"15","hidden":true,"returns":true},{"name":"knapsack([i % 10 + 1 for i in range(60)], [i * 13 % 37 + 1 for i in range(60)], 200)","stdin":"","call":"knapsack([i % 10 + 1 for i in range(60)], [i * 13 % 37 + 1 for i in range(60)], 200)\n","expect":"979","hidden":true,"big":true,"returns":true},{"name":"knapsack([i * 7 % 23 + 1 for i in range(200)], [i * 13 % 37 + 1 for i in range(200)], 1000)","stdin":"","call":"knapsack([i * 7 % 23 + 1 for i in range(200)], [i * 13 % 37 + 1 for i in range(200)], 1000)\n","expect":"2752","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[]},{"task":"Two friends split a pile of parcels to carry home, and `numbers` holds the parcels' weights. Every parcel goes to one of the two, and the loads should be as even as possible. Write `min_difference(numbers)`: the smallest possible difference between the two loads.\n\nThe lighter load is at most half the total, so collect every load it could be, up to half the total, with a set as in the lesson. The heaviest of those, `s`, is the most even split: the other friend carries `total - s`, and the difference is `total - 2 * s`. The tests have 200 weights up to 100, where trying every split never ends.\n\nFor example, `min_difference([8, 3, 5, 9, 2])` returns 1 (8 + 5 is 13, against 3 + 9 + 2 = 14):\n\n```\n1\n```\n","kind":"code","seed":"def min_difference(numbers):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(min_difference([8, 3, 5, 9, 2]))\n","solution":"def min_difference(numbers):\n    total = sum(numbers)\n    half = total // 2\n    sums = {0}\n    for x in numbers:\n        sums |= {s + x for s in sums if s + x <= half}\n    return total - 2 * max(sums)\n\n\nif __name__ == \"__main__\":\n    print(min_difference([8, 3, 5, 9, 2]))\n","hints":["Work out `total = sum(numbers)` and `half = total // 2`. The lighter load never weighs more than `half`, so bigger sums can be thrown away.","Start from `sums = {0}`, an empty load. For each weight `x`, add `{s + x for s in sums if s + x <= half}` to the set with `|=`.","The biggest load in the set, `max(sums)`, is the closest to half. The other friend carries `total - max(sums)`, so the difference is `total - 2 * max(sums)`."],"tests":[{"name":"min_difference([8, 3, 5, 9, 2])","stdin":"","call":"min_difference([8, 3, 5, 9, 2])\n","expect":"1","hidden":false,"returns":true},{"name":"min_difference([4, 7, 2, 5])","stdin":"","call":"min_difference([4, 7, 2, 5])\n","expect":"0","hidden":true,"returns":true},{"name":"print(min_difference([12]), min_difference([]))","stdin":"","call":"print(min_difference([12]), min_difference([]))\n","expect":"12 0","hidden":true},{"name":"min_difference([10, 10, 1])","stdin":"","call":"min_difference([10, 10, 1])\n","expect":"1","hidden":true,"returns":true},{"name":"print(min_difference([1, 10]), min_difference([3, 10]))","stdin":"","call":"print(min_difference([1, 10]), min_difference([3, 10]))\n","expect":"9 7","hidden":true},{"name":"min_difference([2, 4, 8, 16, 33])","stdin":"","call":"min_difference([2, 4, 8, 16, 33])\n","expect":"3","hidden":true,"returns":true},{"name":"min_difference([4, 5, 6, 7, 8])","stdin":"","call":"min_difference([4, 5, 6, 7, 8])\n","expect":"0","hidden":true,"returns":true},{"name":"min_difference([3, 3, 2, 2, 2])","stdin":"","call":"min_difference([3, 3, 2, 2, 2])\n","expect":"0","hidden":true,"returns":true},{"name":"min_difference([i * 37 % 100 + 1 for i in range(40)])","stdin":"","call":"min_difference([i * 37 % 100 + 1 for i in range(40)])\n","expect":"0","hidden":true,"big":true,"returns":true},{"name":"min_difference([i * 37 % 100 + 1 for i in range(200)])","stdin":"","call":"min_difference([i * 37 % 100 + 1 for i in range(200)])\n","expect":"0","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[]}]}