Dynamic Programming · 2 of 60
Knapsack Problem
Pack a bag that can only carry so much weight with the set of toys that gives the most fun, taking each toy whole or not at all.
- Simple way
- time O(2^n)
room O(n) - Clever way
- time O(n * W)
room O(n * W)
The problem, simply
You have n things. Each one has a weight and a value. Your bag can carry at most W weight. Pick some things so the total weight is at most W and the total value is as big as possible. You cannot cut a thing in half, and you cannot take the same thing twice.
Give back the biggest total value. This is the 0/1 knapsack problem: for each thing, 0 means leave it, 1 means take it.
A story to remember it
Picture it
Four toys, each written as weight and fun points. The bag holds 6 kg. Row i may use the first i toys, columns are bag sizes from 0 to 6. The bottom right box will be the answer.
- answer
Examples
The picnic
- In
weights = [1, 4, 2, 2], values = [4, 8, 5, 5], W = 6- Out
14
Ball and both puzzles: 5 kg, 4 + 5 + 5 = 14. Taking the kite first only reaches 13.
The textbook case
- In
weights = [1, 3, 4, 5], values = [1, 4, 5, 7], W = 7- Out
9
Take the 3 kg and 4 kg things for 4 + 5 = 9. The 5 kg thing worth 7 plus the 1 kg thing only gives 8.
Nothing fits
- In
weights = [5, 6], values = [10, 20], W = 4- Out
0
Every thing is heavier than the bag, so the bag stays empty.
A bag of size zero
- In
weights = [1, 2], values = [3, 4], W = 0- Out
0
Column 0 of the table is all zeros. The code must not index below 0.
Way 1: the simple way
Look at the last toy. There are only two choices.
- If there are no toys left or no room left, the fun is 0.
- If the toy is heavier than the room left, you must skip it.
- Otherwise try both: skip it, or take it (add its value and lose its weight). Keep the better one.
Each toy doubles the number of choices, and different choices often land on the same question, like "2 toys left and 4 kg of room".
Each circle is (toys left, room left). The branch marked s skips the toy, t takes it. Faded circles are dead ends with no toys or no room. The two circles marked again were already solved on another branch.
- rejected
- ignored
class Solution {
// i = how many toys we still consider, room = weight we can still carry
public int best(int[] wt, int[] val, int i, int room) {
if (i == 0 || room == 0) return 0; // no toys or no room
if (wt[i - 1] > room) return best(wt, val, i - 1, room); // too heavy, skip
int skip = best(wt, val, i - 1, room);
int take = val[i - 1] + best(wt, val, i - 1, room - wt[i - 1]);
return Math.max(skip, take);
}
public int knapsack(int[] wt, int[] val, int W) {
return best(wt, val, wt.length, W);
}
}How long it takes: each toy splits the work in two, O(2^n). Extra room: the chain of waiting calls is at most n deep, O(n).
Way 2: the clever way
The question "best fun with the first i toys and a bag of size c" only has (n + 1) * (W + 1) versions. Answer each one once in a table, called dp.
- Row 0 (no toys) and column 0 (no room) are all 0.
- For toy
iand bag sizec, first copy the box above: that is "leave toyi". - If toy
ifits, also try "take it": its value plus the box in the row above at columnc - weight. Keep the bigger. - The bottom right box
dp[n][W]is the answer.
Fill the bag table row by row
01 / 15
Row "none" means no toys are allowed yet. Whatever the bag size, the fun is 0.
- looking now
- checking
- done
- answer
- path
- just added
class Solution {
public int knapsack(int[] wt, int[] val, int W) {
int n = wt.length;
int[][] dp = new int[n + 1][W + 1]; // row 0 and column 0 stay 0
for (int i = 1; i <= n; i++) {
for (int c = 0; c <= W; c++) {
dp[i][c] = dp[i - 1][c]; // leave toy i
if (wt[i - 1] <= c) { // toy i fits
dp[i][c] = Math.max(dp[i][c],
val[i - 1] + dp[i - 1][c - wt[i - 1]]); // take toy i
}
}
}
return dp[n][W];
}
// Same answer with one row. Go from big bags to small ones,
// so every toy is used at most once.
public int knapsackOneRow(int[] wt, int[] val, int W) {
int[] dp = new int[W + 1];
for (int i = 0; i < wt.length; i++) {
for (int c = W; c >= wt[i]; c--) {
dp[c] = Math.max(dp[c], val[i] + dp[c - wt[i]]);
}
}
return dp[W];
}
}How long it takes: one quick step per box, O(n * W). Extra room: the table, O(n * W), or O(W) with the one row version. Note that W is a number, not a count of things, so a huge W makes this slow. That is why knapsack is called pseudo-polynomial.
Watch out for
- Greedy by biggest value or by best value per kg is not safe for 0/1 knapsack. It only works for the fractional version, where you may cut things.
- Index slips: toy
iin the table iswt[i - 1]andval[i - 1]in the arrays. W = 0or no items: the answer is 0, and the loops must not readdp[-1].- A memo table for the recursion needs
-1as "not solved yet", since 0 is a real answer.
What interviewers ask next
- Which toys did you take? Walk up from the bottom right box. If a box differs from the box above it, that toy was taken; move left by its weight.
- What if each toy can be taken many times? Unbounded knapsack: use one row and loop
cupward, or usedp[i][c - w]instead ofdp[i - 1][c - w]. - What if
Wis huge but values are small? Flip the table: for each total value, store the smallest weight that reaches it. That costsO(n * total value). - Can you split items into two equal halves? That is subset sum, a knapsack where value equals weight and you ask if
sum / 2is reachable. - What if
nis about 40 andWis huge? Meet in the middle: list all subsets of each half (about a million each), sort one side, and match with binary search.
Remember this
Grown-up words: the backpack is the knapsack, fun points are values, the chart is the DP table, the take or leave choice is the 0/1 decision, and the one row trick is space optimization with a reverse loop.