The archetypal DP family. Given items with weights and values and a capacity, maximise value.
0/1 Knapsack — each item at most once
long long knapsack01(int n, int W, vector<int>& wt, vector<long long>& val) {
vector<long long> dp(W + 1, 0);
for (int i = 0; i < n; i++)
for (int w = W; w >= wt[i]; w--) // DOWN: previous row
dp[w] = max(dp[w], dp[w - wt[i]] + val[i]);
return dp[W];
}time, space. Pseudo-polynomial — it is exponential in the number of bits of , which is why knapsack is NP-hard despite this simple algorithm.
Unbounded Knapsack — unlimited copies
for (int i = 0; i < n; i++)
for (int w = wt[i]; w <= W; w++) // UP: current row
dp[w] = max(dp[w], dp[w - wt[i]] + val[i]);The loop direction is the entire difference between the two problems. Downward reads the previous row (item used at most once); upward reads the current row (item reusable).
Bounded Knapsack — at most copies
Naive is . Two better ways:
Binary splitting. Replace copies of an item by items of multiplicity — items that can form any count up to . Then run 0/1 knapsack.
for (int k = 1; c > 0; k <<= 1) {
int take = min(k, c);
items.push_back({wt * take, val * take});
c -= take;
}.
Monotone deque. Group weights by residue mod and use a sliding-window maximum. — optimal, but longer to write.
Fractional Knapsack — items divisible
Greedy, not DP: sort by value density and take greedily, splitting the last item. . This is the one knapsack variant that is not NP-hard, and the exchange argument proving it is a good example of greedy correctness.
The variant table
| Variant | Recurrence change | Complexity |
|---|---|---|
| 0/1 | weight loop down | |
| Unbounded | weight loop up | |
| Bounded ( copies) | binary splitting | |
| Fractional | greedy by density | |
| Count the ways instead of maximising | dp[w] += dp[w - wt] | |
| Subset sum (feasibility only) | boolean, use a bitset | |
| Exactly fill capacity | initialise dp[0]=0, rest | |
| Minimum items to reach | dp[w] = min(dp[w-wt]+1) | |
| Small , huge values | swap the roles: dp[value] = min weight | |
| Huge , few items () | meet in the middle | |
| Multi-dimensional (weight and volume) | dp[w][v] | |
| knapsacks | dp[w1][w2]... — exponential in |
The “swap the roles” row is the one people forget: when is but , index the DP by value and store the minimum weight needed. Always check which quantity is small.
Reconstructing the chosen items
Keep the full 2D table (or a take[i][w] bitset) and walk backwards:
int w = W;
for (int i = n - 1; i >= 0; i--)
if (dp[i + 1][w] != dp[i][w]) { chosen.push_back(i); w -= wt[i]; }With only the 1D array, this is impossible — the trade-off for the memory saving.
Related classics
- Subset sum and partition — knapsack with
- Coin change — unbounded knapsack, counting or minimising
- Rod cutting — unbounded knapsack in disguise
- Karmarkar-Karp — a heuristic for balanced partitioning
See also: Coin Change · Partition Problems · Bitset Optimization