Knapsack Problems
Given a budget and a set of items each with a cost and a value, which items maximize value without exceeding the budget? The 0/1 and unbounded variants differ by one line of code and one big idea: can you reuse an item.
Prerequisites: Memoization vs Tabulation, Big-O Complexity
You have a fixed capital budget and a list of trade ideas, each with a required capital outlay and an expected payoff. Which subset of ideas maximizes total payoff without exceeding the budget? This is the knapsack problem, and it's a genuinely useful mental model for capital-constrained selection problems, not just a textbook exercise.
The idea: a table indexed by items considered and capacity used
In the 0/1 knapsack (each item taken at most once), define as the best achievable value using only the first items with capacity budget . For each item, you have exactly two choices: skip it, keeping , or take it (if it fits), gaining its value plus the best you could do with the remaining capacity using only earlier items — . The recurrence takes the better of the two:
In words: either this item isn't worth its cost given what's left, or it is, and the recurrence checks both possibilities and keeps the winner. Time and space are where is the capacity — polynomial in the capacity's value, not its number of digits, which is why knapsack is only "efficient" for moderate budgets, not for a budget of, say, $10 billion in cent-sized units.
The unbounded knapsack (unlimited copies of each item — think a bond you can buy in unlimited size) drops the "" on the take-branch, since reusing the same item is now allowed: , iterating capacity forward instead of backward.
Worked example
Budget 5. Items: (weight 2, value 3), (weight 3, value 4), (weight 4, value 5). Building for : with only item 1 (), row is [0,0,3,3,3,3]. Adding item 2 (): at , skip gives 3 (previous row), take gives — take wins, so (items 1 and 2, weight , value ). Adding item 3 () at : skip gives 7, take gives — skip wins. Final answer: 7, using items 1 and 2.
def knapsack_01(weights, values, capacity): # O(n * capacity)
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
for c in range(capacity, weights[i] - 1, -1): # reverse: 0/1, no reuse
dp[c] = max(dp[c], values[i] + dp[c - weights[i]])
return dp[capacity]
0/1 knapsack: iterate capacity backward so each item is used at most once. Unbounded knapsack: iterate capacity forward so an item can feed off its own already-updated value, allowing reuse. That single loop direction is the entire difference between the two problems.
"Subset sum" and "partition into two equal-sum subsets" are 0/1 knapsack with value equal to weight — recognizing that reduction turns an unfamiliar problem into a familiar template instantly.
Where it shows up
Interview staples: partition equal subset sum, coin change (unbounded knapsack), target sum. On a desk, knapsack-style reasoning appears in capital-constrained trade selection — picking which positions to hold under a margin or capital budget to maximize expected P&L — and in bin-packing-adjacent problems like allocating a fixed risk budget across strategies with different capacity requirements and expected returns.
Practice in interviews
Further reading
- Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (ch. 15, DP)