Knapsack Problem
Knapsack Problem
Category
Dynamic Programming
Difficulty
Hard
Problem Statement
Given a list of items, each with a weight and a value, and a knapsack with a fixed capacity, determine the maximum total value you can fit into the knapsack. Each item can either be included or excluded (0/1 constraint) — you cannot take a fractional part of any item.
Intuition
At each item, we face a binary choice: take it or leave it. A greedy approach fails because selecting the locally best item (by value, weight, or ratio) does not guarantee a globally optimal combination. Dynamic programming works because the problem has optimal substructure (the best solution for capacity c with i items builds on the best solution for smaller sub-problems) and overlapping sub-problems (the same (item index, remaining capacity) state is encountered many times in a naive recursion).
Approach
- Create a 2D table
dpof size(n+1) x (capacity+1), initialized to 0. - For each item
ifrom 1 ton:- For each capacity
cfrom 0 tocapacity:- If
weights[i-1] <= c, setdp[i][c] = max(dp[i-1][c], dp[i-1][c - weights[i-1]] + values[i-1]). - Otherwise, set
dp[i][c] = dp[i-1][c].
- If
- For each capacity
dp[n][capacity]holds the maximum value.- To recover the chosen items, backtrack from
dp[n][capacity]: ifdp[i][c] != dp[i-1][c], itemiwas included — add it, subtract its weight fromc, and move to rowi-1.
Pseudocode
function knapsack(items, capacity):
n = length(items)
dp = 2D array of size (n+1) x (capacity+1), filled with 0
for i from 1 to n:
weight = items[i-1].weight
value = items[i-1].value
for c from 0 to capacity:
if weight <= c:
dp[i][c] = max(dp[i-1][c], dp[i-1][c - weight] + value)
else:
dp[i][c] = dp[i-1][c]
// Backtrack to find chosen items
result = []
c = capacity
for i from n down to 1:
if dp[i][c] != dp[i-1][c]:
result.append(items[i-1])
c -= items[i-1].weight
return dp[n][capacity], result
Time & Space Complexity
| Measure | Complexity | Justification |
|---|---|---|
| Time | O(n * C) | Two nested loops: n items and C capacity values |
| Space | O(n * C) | Size of the DP table (reducible to O(C) if item recovery is not needed) |
Key Insights
- This is a pseudo-polynomial algorithm — polynomial in
nandC, butCis not polynomial in the size of the input (number of bits to representC). - Space can be optimized to a single 1D array of size
C+1by iterating capacity right to left in the inner loop, but this sacrifices the ability to reconstruct which items were chosen. - The 0/1 knapsack is NP-Hard in general; the DP solution is efficient only when
Cis reasonably small. - A common variant is the unbounded knapsack where each item can be used multiple times; the recurrence changes to reference the same row (
dp[i]) instead of the previous row (dp[i-1]).
Edge Cases
- Capacity is 0: no items can be taken; answer is 0.
- All items weigh more than capacity: answer is 0, empty set.
- Single item: include it if its weight fits, otherwise 0.
- Multiple items with the same weight and value: each is still treated independently.
- Very large capacity with small item weights: table can become very large — consider space optimization.