0/1 Knapsack is the gateway problem for two-dimensional dynamic programming. Each item is either fully taken or fully left behind — there is no taking half of it — and you want the most valuable haul that still fits in a weight-limited bag.
Problem. You have n items; item i has weight wt[i] and value val[i]. Given a knapsack
capacity W, pick a subset of items whose total weight is at most W and whose total value is as
large as possible. Each item may be used at most once (that is the 0/1 rule).
Example: wt = [1, 3, 4], val = [15, 20, 30], W = 5 → best value 45 (take items 1 and 3: weight
1 + 4 = 5, value 15 + 30 = 45).
The slow way first
The brute-force idea: for every item decide take-or-skip, so there are 2^n subsets. Try them all, keep the best feasible one. That is O(2^n) — it explodes the moment n passes about 20.
The question to ask: what decision am I really making, and what do I need to know to make it? For each item I choose take or skip, and to compare those two choices I only need the best value I could get with the earlier items at the relevant capacity. That is a subproblem I can remember instead of recomputing.
The idea: a grid of subproblems
Define dp[i][w] = the best total value using only the first i items within capacity w. Build the grid row by row. For each cell there are two choices:
- Skip item
i: the value isdp[i-1][w]— same capacity, one fewer item. - Take item
i(only ifwt[i] <= w): the value isval[i] + dp[i-1][w - wt[i]]— its value plus the best you could do with the remaining capacity using earlier items.
Keep the larger of the two. The answer is the bottom-right cell, dp[n][W].
The key insight: every cell is decided by exactly two cells in the row directly above it. That is why a single pass over the grid, top to bottom, is enough.
Walk through it
Step through the animation. The grid is (n+1) rows by (W+1) columns; row 0 and column 0 are all zero. For each cell we light up the two cells it reads from the row above — the skip cell straight up, and the take cell up-and-left by the item weight — then store the larger result. When item 1 (weight 1) cannot fit a smaller capacity it just copies the value above; when items fit, the take branch wins and values climb toward the final 45.
Pseudocode
n = number of items
make a grid dp with (n+1) rows and (W+1) columns, all 0
for i from 1 to n:
for w from 0 to W:
skip = dp[i-1][w] # leave item i out
if weight of item i <= w: # does it fit?
take = val[i] + dp[i-1][w - wt[i]] # put it in, fill the rest
dp[i][w] = max(skip, take) # keep the better choice
else:
dp[i][w] = skip # too heavy, must skip
return dp[n][W] # best value, all items, full capacityThe Python solution
def knapsack(wt, val, W):
n = len(wt)
dp = [[0] * (W + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(W + 1):
skip = dp[i - 1][w]
if wt[i - 1] <= w:
take = val[i - 1] + dp[i - 1][w - wt[i - 1]]
dp[i][w] = max(skip, take)
else:
dp[i][w] = skip
return dp[n][W]dp[i][w]is the best value using the firstiitems within capacityw; the extra row and column of zeros mean no items or no capacity.skip = dp[i - 1][w]is the value if we leave itemiout — read the same column one row up.wt[i - 1] <= wis the fit check (the list is 0-indexed, so itemilives at indexi - 1).take = val[i - 1] + dp[i - 1][w - wt[i - 1]]adds itemivalue to the best we could do with the leftover capacityw - wt[i - 1]using earlier items.dp[i][w] = max(skip, take)keeps the better of the two choices; if the item is too heavy, onlyskipis possible.- The final answer is
dp[n][W]— all items considered, full capacity available.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (every subset) | O(2^n) (slow) | try take/skip for all n items |
| DP grid (this solution) | O(n * W) (moderate) | fill each cell once in O(1) |
O(n * W) (moderate)The DP is pseudo-polynomial: it depends on the numeric capacity W, not just the item count. The grid uses O(n * W) space, which a one-row rolling array can shrink to O(W) by overwriting right-to-left — but the two-row grid is easier to read and to reconstruct which items were chosen.
When this pattern shows up
Any time each element has a take-or-skip choice and there is a budget or capacity to respect, think 0/1 Knapsack: a 2-D grid indexed by items so far and remaining budget. Subset-sum, partition-equal- subset, target-sum, and many counting problems are the same grid with a tweaked recurrence.
Watch the indexing: item i in the loop (1-based) is wt[i - 1] and val[i - 1] in the 0-based lists.
And only consider the take branch when wt[i - 1] <= w — otherwise w - wt[i - 1] goes negative and
reads the wrong cell. This is 0/1 (each item once); the unbounded variant reads dp[i][...] instead of
dp[i - 1][...].
Practice
Filling dp[2][3] for wt=[1,3,4], val=[15,20,30]: item 2 weighs 3 and fits in capacity 3. What two cells does it compare, and what value wins?
1. What does dp[i][w] represent?
2. Which two cells does the take-or-skip choice read?
3. Why must we check wt[i - 1] <= w before taking the item?
4. What is the time complexity of the DP solution?