Knapsack Problems
A bag with a fixed capacity, and a set of items that each have a weight and a value — which items give the most value? The model itself is small. What makes it worth a chapter is how many problems are this problem in another form: splitting an array, making change, cutting a sentence into words, rolling dice. This chapter turns the take-or-skip decision from Chapter 7 into a fixed modeling procedure, and explains the one line that is most often written the wrong way round: in the one-dimensional form, does the capacity loop run down or up?
Why a knapsack: take-or-skip, plus a capacity limit
The decision model from House Robber in Chapter 7, with one extra rule: the total weight cannot exceed the capacity.
In House Robber each house had two options: rob it or skip it. The knapsack problem is almost the same. Each item has two options: take it or leave it. The one new element is a capacity limit: the weights of the items you take must add up to no more than the capacity of the bag. Pack the bag yourself first and see how the limit changes the decisions.
What does brute force cost? Each item is taken or not, so n items give 2ⁿ subsets. Checking every subset's weight and value is about a billion steps at n = 30, which is far too slow. That is the same growth as the backtracking tree in Chapter 5. Why can it be improved? Because the same subproblem is asked many times. "The best value from the first 3 items with capacity 5" is reached by many different decision paths, and the answer is the same every time. Overlapping subproblems plus optimal substructure — the best answer for a smaller capacity is part of the best answer for a larger one — are exactly the two conditions DP needs.
📦 Four questions that turn a problem into a knapsack
① What is an item (the thing you decide on, one at a time)? ② What is the capacity (the limited resource — often a sum, an amount, or a length)? ③ How many times may one item be used (at most once = 0/1 knapsack; any number of times = unbounded knapsack; at most k times = bounded knapsack)? ④ What is being asked (largest value / can it be filled exactly / how many ways / fewest items)? Answer these four and the code is nearly fixed.
Each item is used at most once. This is the base form: LC 416, LC 494, and LC 474 are all 0/1 knapsacks. In the one-dimensional form the capacity loop runs downward.
Each item may be used any number of times. LC 322, LC 279, and LC 139 are unbounded. In the one-dimensional form the capacity loop runs upward — one line different from the 0/1 form.
Bounded: item i may be used at most k times. Splitting it into k separate 0/1 items always works. Grouped: items come in groups and you take at most one, or exactly one, from each group. LC 1155 (dice) is the grouped form: every die contributes exactly one face. §07 comes back to both.
The knapsack problem is old, and it is still NP-hard
The knapsack problem is one of the most studied problems in operations research, with work on it going back more than a century. It has also been used in cryptography: the 1978 Merkle–Hellman scheme took its security from the difficulty of subset sum, and was broken by Shamir in 1982.
One thing to be careful about: the O(nW) solution in this chapter is not a polynomial-time algorithm for the general problem. W is a value, and writing that value down takes only about log W digits, so O(nW) is exponential in the size of the input. An algorithm whose cost is polynomial in the numeric values, but not in the input length, is called pseudo-polynomial. The 0/1 knapsack problem is NP-hard, and no polynomial-time algorithm for it is known. The problems in this chapter are solvable because their capacities are small: LC 416 caps the total at 20000, LC 322 caps the amount at 10⁴.
The 0/1 knapsack, starting from a two-dimensional table
Foundationdp[i][j] = the largest value using the first i items with weight at most j — take-or-skip written as a recurrence.
Follow the five steps from Chapter 7. Define the state in words first: dp[i][j] = the largest value you can reach when only the first i items are available and the total weight is at most j. Then write the transition. Standing in front of item i, there are exactly two options:
- Skip it: the value is the best you could do with the first i−1 items and the same capacity j, which is
dp[i−1][j]. - Take it: first free w[i] of capacity, then add its value, which is
dp[i−1][j−w[i]] + v[i]. This option only exists when j ≥ w[i].
Take the larger of the two. Base case: row 0 is all zeros, because with no item available the value is 0 at every capacity. The answer is dp[n][W], the bottom-right cell. Watch the table fill in, cell by cell. A dashed blue cell is a value the current cell reads.
Why does "take it" read dp[i−1][j−w] and not dp[i][j−w]?
Because there is only one copy of item i. To take it you must free capacity from a state where item i has not been considered yet, and that state is row i−1. Reading dp[i][j−w] instead would allow item i to be taken again, and again. That single character turns the 0/1 knapsack into the unbounded knapsack, which is the subject of §07. Keep this correspondence in mind: 0/1 reads the previous row, unbounded reads the current row. §03 shows what each of them becomes once the table is rolled into one row.
The rolling array: where the loop direction comes from
COMMON MISTAKERoll the table into one row, and the 0/1 knapsack must run the capacity downward. Watch what going up does instead.
Look at the transition again: dp[i][j] reads only two cells, and both are in the previous row. Once row i−1 has been used, it is never needed again. So there is no reason to keep the whole table. Keep one row and overwrite it. The two-dimensional dp[i][j] becomes the one-dimensional dp[j], and each item is one full sweep of that row. After the sweep the row holds exactly the numbers of the matching table row.
Rolling the table introduces one question that did not exist before. When the sweep writes dp[j], it reads dp[j−w]. Is that the old value from the previous row, or a value this same sweep has already overwritten? In the 0/1 knapsack the transition needs dp[i−1][j−w], which is the old value — the state in which this item has not been used yet. The direction of the sweep is what decides which of the two you get. Switch between the two buttons below: same item, same array, two directions.
j goes from large to small. When dp[j] is written, dp[j−w] has not been touched in this sweep, so it is still the previous row. The item enters the bag at most once, which is what the 0/1 knapsack means.
j goes from small to large. By the time dp[4] is written, this sweep has already set dp[2] to 3, so dp[2] contains one copy of the item. Adding 3 again gives 6: the same item was placed in the bag twice.
range(W, w[i] - 1, -1) counts down from W to w[i]. The stop value is w[i]-1 because the right end of a Python range is excluded. Forgetting the −1 skips the cell j = w[i].Say it in one paragraph, without memorizing a rule
"The one-dimensional array is the two-dimensional table rolled into a single row. The 0/1 transition reads dp[i−1][j−w], a cell in the previous row, so I sweep the capacity downward: then dp[j−w] has not been overwritten yet and still holds the previous row, and each item enters the bag at most once. If I sweep upward, dp[j−w] comes from the current row, which already includes this item — that is the unbounded knapsack, not this one." The direction is not a rule to remember. It follows from which row you need to read, and you can re-derive it every time.
Fill exactly: from largest value to can it be filled
MEDIUMWorked example A · LC 416 Partition Equal Subset Sum — a reduction to the 0/1 knapsack you already have.
The problem: given an array of positive integers, can it be split into two subsets with equal sums? The reduction: if the two sums are equal, each one is sum/2. So an odd total makes a split impossible. Otherwise the question becomes: can some of the numbers add up to exactly sum/2? That is a 0/1 knapsack — each number is an item used at most once, sum/2 is the capacity — but instead of the largest value it asks whether the bag can be filled exactly.
Why does the same code work? Because "largest value" and "can it be filled" use the same take-or-skip skeleton. Only the operator changes, from max to boolean or: dp[j] = "j was already reachable" || "j−num was reachable and we add one num". Nothing here is a new algorithm; it is the same table with a boolean in each cell.
bits |= bits << x performs the entire sweep for one number in a single shift, with a very small constant factor. Chapter 4 covers using an integer as a set.Split the stones into two piles with the smallest possible difference, so one pile should get as close to sum/2 as it can without going over. Capacity sum/2, and each weight is both the cost and the value. Find the largest reachable weight maxHalf; the answer is sum − 2 × maxHalf. LC 416 asks can it be filled; this one asks how full can it get.
Replace "can it be filled" with "in how many ways can it be filled", which is the next section. The three questions in order: can it, how full, how many ways.
The statement says "split into two halves", "reach this sum", or "equal to exactly". Compute the total first, derive the target capacity from it, and you usually have a 0/1 knapsack that asks for an exact fill.
Counting: the same problem as a tree of 2ⁿ paths, and as a table
MEDIUMWorked example B · LC 494 Target Sum — backtracking and knapsack side by side.
The problem: put a + or a − in front of every number in nums so the expression equals target, and count how many ways there are. As backtracking: each number has two options, so the search is a binary decision tree of depth n. Walk to the bottom and count the leaves whose sum is target. The logic is right, but the cost is O(2ⁿ):
How does this become a knapsack? Through one algebraic step. Let P be the sum of the numbers that get a plus sign, and N the sum of the absolute values of the rest. Then:
So the question becomes: how many subsets add up to exactly P? — a counting 0/1 knapsack. If P is not a whole number, is negative, or exceeds sum, the answer is 0.
The rest is the LC 416 skeleton with the boolean or replaced by addition of counts: dp[j] += dp[j−num]. The number of ways to make j is the ways already counted, plus the ways to make j−num followed by this num. Base case dp[0] = 1, because the empty subset is one way to make 0. The capacity still runs downward, because each number is used at most once.
dp[j] += dp[j], which doubles every cell. No special case is needed.Where counting DP shows up outside interviews
"How many ways can this total be reached" is a common question in practice: the probability that n dice sum to s (LC 1155), a change machine reporting how many ways a payment can be made, or a parser counting how many valid parse trees a sentence has. All of them reduce to the same line, dp[j] += dp[j−x], and all of them count without listing.
Two costs: one item spends two kinds of capacity
MEDIUMLC 474 Ones and Zeroes — add one dimension to the table, and the skeleton does not change.
The problem: given a list of binary strings and a budget of at most m zeros and n ones, how many strings can you choose? The observation: each string is a 0/1 item, but it spends two resources at once — the zeros it contains and the ones it contains. That is a two-cost knapsack: two limits instead of one, so the table gets one more dimension.
State: dp[i][j] = the largest number of strings you can choose using at most i zeros and j ones. The transition is still take-or-skip this string; the only change is that taking it frees two kinds of capacity: dp[i][j] = max(dp[i][j], dp[i−zeros][j−ones] + 1). Each string is still used at most once, so both capacity loops run downward, for exactly the reason given in §03.
s.count("0") gives the number of zeros in one call, and ones is the length minus that. Computing both costs of an item before the loops keeps the inner lines readable.One limited resource, one dimension
Capacity in a knapsack is just a limited resource. One resource gives a one-dimensional table, two resources (zeros and ones) give two dimensions, three give three. Each new dimension takes its own loop direction from the rule for that resource: descending if the item is used at most once, ascending if it may be reused. The skeleton stays take-or-skip. Note the cost: the number of states is the product of all the limits, so a third budget can make the table too large to be practical.
The unbounded knapsack: ascending capacity is what allows reuse
MEDIUMLC 322 Coin Change modelled again (Chapter 7 solved it another way), plus LC 279 and LC 139.
In an unbounded knapsack each item may be taken any number of times. Recall why going up was wrong in §03: dp[j−w] had already been updated in the same sweep, so it already contained one copy of the item, and the item was counted twice. For the unbounded knapsack that is not a bug — reusing an item is exactly what the problem allows. So the code is the 0/1 code with one direction reversed.
Capacity descending. dp[j−w] is still the value from before this item was processed — in table terms, row i−1. Each item enters the bag at most once.
Capacity ascending. dp[j−w] may already include this item — in table terms, row i itself. So the same item can be added again and again.
The two-dimensional form makes the same distinction visible in one character. The 0/1 transition is dp[i][j] = max(dp[i−1][j], dp[i−1][j−w] + v). The unbounded transition is dp[i][j] = max(dp[i−1][j], dp[i][j−w] + v) — it reads its own row, the row in which item i may already have been taken. Rolling each of them into a single array gives exactly the two directions above: reading the previous row means reading a cell this sweep has not touched, which is the descending order, and reading the current row means reading a cell this sweep has already written, which is the ascending order.
LC 322 Coin Change makes a good test, because Chapter 7 already solved it a different way: there, dp[amount] was filled by asking which coin is the last one. Here the model is an unbounded knapsack — coins are items with unlimited supply, the amount is the capacity — and the array is filled one coin type at a time, ascending. Two models, one answer:
amount + 1 works as "unreachable": any amount that can be made needs at most amount coins, so a real answer never reaches amount + 1. Adding 1 to it also cannot overflow, which is a risk with a very large sentinel value.For min and max, the loop order does not matter. For counting, it decides the answer.
In an unbounded knapsack that asks for a smallest or largest value — LC 322 and LC 279 — you may put either loop on the outside and both give the correct answer. min only cares about the best of all the ways to make j, and the best does not depend on the order the items were considered in. Counting is different. In LC 518 and LC 377 the loop order decides whether you count combinations or permutations, and the two numbers are not the same. The next section is about exactly that.
Treat 1, 4, 9, 16 ... as denominations with unlimited supply and find the fewest of them that add up to n. The code is LC 322 with a different item list. Time O(n√n), because there are about √n squares below n.
Dictionary words may be reused, and order matters, because the result is one sentence. So the capacity (the prefix length) is the outer loop and the words are the inner loop — the same nesting LC 377 uses. The next section explains why.
Between 0/1 and unbounded: item i may be used at most k times. The direct method copies it into k separate 0/1 items. A better one is binary splitting: replace k copies with items of size 1, 2, 4, ... so any count from 0 to k can still be formed, which turns k items into about log k.
Combinations or permutations: which loop is on the outside decides
MEDIUMWorked example C · LC 518 counts combinations, LC 377 counts permutations, and the code differs by one line.
Counting problems on the unbounded knapsack have one detail that is easy to get wrong. Two problems can both ask "how many ways make the amount n", both use dp[j] += dp[j−coin], and still count different things, because of the order of the two loops.
1 + 2 and 2 + 1 count as one way. Each coin type is introduced in its own pass, and always after the coins before it, so one set of coins is only ever reached in one order. Order is not counted: these are combinations.
1 + 2 and 2 + 1 count as two ways. At every capacity all the numbers get a turn as the last one added, so the two orders land in separate counts. Order is counted: these are permutations.
Start with LC 518, combinations: coins = [1,2,5] and amount 5. Coins on the outside means the passes happen in a fixed order — first count every way that uses only ¥1, then bring in ¥2, then ¥5. ¥2 always enters after ¥1, so no way is ever counted twice under a different ordering. Step through it:
How to tell which one a problem wants
Items outside, capacity inside → combinations (LC 518): the items enter in a fixed order, so orderings are not counted separately.
Capacity outside, items inside → permutations (LC 377, LC 139): every item gets a turn as the last one added at each capacity, so orderings are counted separately.
To decide, ask one question about the problem statement: do 1 + 2 and 2 + 1 count as one answer or two? One answer means the LC 518 nesting; two answers means the LC 377 nesting. Note that LC 377 is named "Combination Sum IV" but counts permutations, so read the examples rather than the title.
Problem set: ten knapsack problems
Core setOrdered as fill exactly → counting → two costs → unbounded → combinations and permutations → grouped. Think for 30 seconds before opening a hint.
Quiz
✎ QuizAnswer all 8 correctly to mark this chapter complete.
In the one-dimensional form of the 0/1 knapsack, why must the capacity loop run downward?
Run the one-dimensional 0/1 loop upward instead, with a single item of weight 2 and value 3, capacities 0 to 5. What does dp[4] become, and what does that show?
In the one-dimensional unbounded knapsack (every item has unlimited supply), the capacity loop runs upward. Why?
For "how many ways add up to n", LC 518 counts combinations (1+2 and 2+1 are one way) and LC 377 counts permutations (they are two). The code is almost identical. Where is the difference?
LC 416 asks whether an array can be split into two subsets with equal sums. What is the correct reduction to a knapsack?
LC 494 Target Sum, nums = [1,1,1,1,1] and target = 3. Using P = (sum + target) / 2, the problem becomes "how many subsets add up to P?" How many are there?
Which of these are signals to model a problem as an unbounded knapsack (every item has unlimited supply)? (Select all that apply)
For the two-dimensional 0/1 knapsack, where dp[i][j] is the largest value using the first i items with capacity at most j, which transition is correct?
- A knapsack is the take-or-skip model from House Robber plus a capacity limit. State: dp[i][j] = the best result using the first i items with weight at most j. Take = dp[i−1][j−w] + v, skip = dp[i−1][j], base row 0 = 0, answer at dp[n][W].
- In the rolled one-dimensional form, 0/1 runs the capacity downward and unbounded runs it upward. Do not memorise this. Derive it: descending reads dp[j−w] from the previous item's row, ascending reads it from the current item's row, and reading the current row is what lets an item be reused.
- The operator follows the question: largest value → max, can it be filled → or, how many ways → +=, fewest items → min. The skeleton stays the same, so copying the operator from the previous problem is the usual mistake.
- Subset-sum problems are reductions, not new algorithms. LC 416 asks can it be filled, LC 1049 asks how full can it get, LC 494 asks in how many ways — all three are the 0/1 knapsack with a different cell type.
- For counting on an unbounded knapsack: items outside gives combinations (LC 518), capacity outside gives permutations (LC 377, LC 139). The test is one question: do 1 + 2 and 2 + 1 count as one way or two? For min and max problems the order does not matter.
- Two costs (LC 474) means one weight becomes a pair of weights. Each limited resource adds one dimension, and each dimension takes its direction from its own reuse rule. The number of states is the product of the limits.
- The cost is (number of states) × (work per transition). For the basic form that is O(nW) time, and O(nW) space before the rolling reduction, O(W) after. O(nW) is polynomial in the value of W, not in the length of the input, so this does not make the knapsack problem easy — it is still NP-hard.
- Four modeling questions: what is an item · what is the capacity · how many times may one item be used · what is being asked. Answer them and the code is nearly determined.