Dynamic Programming · 8 of 60
Subset Sum Problem
Decide whether you can pick some of the numbers so they add up to exactly a target.
- Simple way
- time O(2^n)
room O(n) - Clever way
- time O(n * sum)
room O(n * sum)
The problem, simply
You get a list of non negative whole numbers and a target sum. Can you choose some of the numbers (each one at most once) so that they add up to exactly sum? Answer yes or no.
Choosing nothing is allowed, so a target of 0 is always a yes.
A story to remember it
Picture it
Coins 3, 4, 5 and 2 with target 6. Paying 4 and 2 works. 3 alone or 5 alone does not reach 6.
- answer
Examples
The classic
- In
nums = [3, 34, 4, 12, 5, 2], sum = 9- Out
true
4 + 5 = 9. Also 3 + 4 + 2.
Not reachable
- In
nums = [3, 34, 4, 12, 5, 2], sum = 30- Out
false
Without 34 the most you can reach is 26, and 34 alone is already too big.
Small coins
- In
nums = [3, 4, 5, 2], sum = 6- Out
true
4 + 2. The walkthrough fills this table.
Target zero
- In
nums = [1, 2], sum = 0- Out
true
Pick nothing. Every list can make 0.
No numbers
- In
nums = [], sum = 5- Out
false
With nothing to pick, only 0 is reachable.
Way 1: the simple way
Go through the numbers one by one. For each, ask two helpers: "can the rest make it if I take this one?" and "can the rest make it if I skip it?"
can(i, need): ifneed == 0, yes.- If there are no numbers left (
i == n), no. - If
nums[i] <= needandcan(i + 1, need - nums[i]), yes. - Otherwise the answer is
can(i + 1, need).
The choices for [3, 4, 5, 2] with need 6. Each circle is (next item, sum still needed), y means take it, n means skip it. Double rings reached 0. The highlighted (4,1) is reached twice: by taking 3 and 2, and by taking 5.
- checking
- answer
class Solution {
public boolean isSubsetSum(int[] nums, int sum) {
return can(nums, 0, sum);
}
private boolean can(int[] nums, int i, int need) {
if (need == 0) return true; // exact price paid
if (i == nums.length) return false; // no coins left
if (nums[i] <= need && can(nums, i + 1, need - nums[i])) return true; // take it
return can(nums, i + 1, need); // skip it
}
}How long it takes: two choices per number, O(2^n) in the worst case. Extra room: the call chain, O(n).
Way 2: the clever way
The helper only depends on (how many numbers looked at, sum needed). There are only (n + 1) x (sum + 1) such pairs. Fill them all in a table.
can[i][s]: can some of the firstinumbers make exactlys?- Column 0 is all true. The rest of row 0 is false.
- For number
xin rowi:can[i][s] = can[i-1][s]or (x <= sandcan[i-1][s-x]). - The answer is
can[n][sum].
Fill the table for coins 3, 4, 5, 2 and target 6
01 / 09
With no items you can only make sum 0 (an empty bag). So row none is T at 0 and F everywhere else.
- looking now
- checking
- done
- answer
- just added
class Solution {
public boolean isSubsetSum(int[] nums, int sum) {
int n = nums.length;
boolean[][] can = new boolean[n + 1][sum + 1];
for (int i = 0; i <= n; i++) can[i][0] = true; // pick nothing
for (int i = 1; i <= n; i++) {
int x = nums[i - 1];
for (int s = 1; s <= sum; s++) {
can[i][s] = can[i - 1][s]; // skip x
if (x <= s && can[i - 1][s - x]) {
can[i][s] = true; // take x
}
}
}
return can[n][sum];
}
}How long it takes: one step per cell, O(n * sum). Extra room: the table, O(n * sum). This is fast only when sum is small. In computer science terms it is "pseudo polynomial": it grows with the value of sum, not with how many digits it has.
Watch out for
- A target of 0 is always true, even for an empty list.
- Negative numbers break the table, because indices like
s - xcan jump pastsum. Shift all sums by the total of the negatives, or use a hash set of reachable sums. - Big targets (in the billions) make the table too large. Then use meet in the middle: split the list in half, list all sums of each half (
2^(n/2)each), sort one side and search forsum - ain it.
What interviewers ask next
- Do it with one row.
can[s] |= can[s - x]forsfromsumdown tox. Room drops toO(sum). - Print one valid subset. Keep the full table, then walk back from
can[n][sum]: ifcan[i-1][s]is true, skip itemi, else take it and move tos - x. - Count the subsets that make
sum. Replace "or" with "+":ways[i][s] = ways[i-1][s] + ways[i-1][s-x]. Watch out for zeros in the list, they double the count. - Partition into two equal halves, or into two halves with the smallest difference. Both are this problem with target
total / 2. - Make it faster with bits: keep the reachable sums as a bitset and do
bits |= bits << x.
Remember this
Grown-up words: the handful of coins is a subset, the yes or no chart is the subset sum DP table, take or skip is the 0/1 knapsack choice, and growing with the value of sum is pseudo polynomial time.