Dynamic Programming · 40 of 60
Partition problem
Decide if a pile of numbers can be split into two groups with exactly the same total.
- 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 positive numbers. Can you put every number into one of two groups so that both groups add up to the same total? Answer yes or no.
The trick to see first: if both groups are equal, each one is exactly half of the grand total. So the question becomes "can some of the numbers add up to half the total?" If the total is odd, the answer is no right away.
A story to remember it
Picture it
Four bars weighing 2, 1, 3 and 4. Total 10, so each kid needs 5. The plate on the left gets 2 and 3.
- done
- path
Examples
A fair split
- In
[2, 1, 3, 4]- Out
true
2 + 3 = 5 and 1 + 4 = 5.
The classic
- In
[1, 5, 11, 5]- Out
true
1 + 5 + 5 = 11 and 11 = 11.
Odd total
- In
[1, 2, 3, 5]- Out
false
The total is 11. You cannot split an odd total into two equal whole halves.
Even total, still no
- In
[1, 2, 5]- Out
false
The total is 8, so you need 4. But 1, 2, 5 can only make 1, 2, 3, 5, 6, 7 and 8. The even check alone is not enough.
One number
- In
[7]- Out
false
The other group would be empty with total 0. Two equal numbers like [3, 3] give true.
Way 1: the simple way
Find the half, then try every pick. Go through the numbers one by one, and for each one ask two helpers: "can the rest make it if I put this one in?" and "can the rest make it if I leave this one out?"
- Add up everything. If the total is odd, return false. Otherwise
need = total / 2. can(i, need): ifneedis 0, yes. If no numbers are left, no.- Otherwise, yes if
can(i + 1, need - a[i])(put it in, only if it fits) orcan(i + 1, need)(leave it out).
The first three choices for [2, 1, 3, 4] with need 5. Each circle is (next item, weight still needed), y means put it in, n means leave it out. The double ring needs 0 more, which is a yes. (3,2) shows up twice: 2 + 1 and 3 both leave 2 needed.
- checking
- answer
class Solution {
public boolean canPartition(int[] arr) {
int total = 0;
for (int x : arr) total += x;
if (total % 2 != 0) return false; // odd total: no fair split
return can(arr, 0, total / 2);
}
private boolean can(int[] arr, int i, int need) {
if (need == 0) return true; // plate is exactly right
if (i == arr.length) return false; // nothing left to try
if (arr[i] <= need && can(arr, i + 1, need - arr[i])) return true; // put it in
return can(arr, i + 1, need); // leave it out
}
}How long it takes: each number has two choices, so up to O(2^n) tries. Extra room: the call chain, O(n).
Way 2: the clever way
The helper only depends on two small numbers: how many items you have looked at, and how much weight is still needed. Put every answer in a table.
can[i][s]is true when some of the firstinumbers add up to exactlys.can[0][0]is true (no numbers, empty plate). Every other cell in row 0 is false.- For number
xin rowi:can[i][s] = can[i-1][s](leave it out) or, ifx <= s,can[i-1][s-x](put it in). - The answer is
can[n][total / 2].
Fill the yes or no table for [2, 1, 3, 4], need 5
01 / 09
All bars weigh 10 together, so each kid must get 5. Now we only ask: can some bars make exactly 5? 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 canPartition(int[] arr) {
int total = 0;
for (int x : arr) total += x;
if (total % 2 != 0) return false;
int half = total / 2;
int n = arr.length;
boolean[][] can = new boolean[n + 1][half + 1];
for (int i = 0; i <= n; i++) can[i][0] = true; // the empty plate
for (int i = 1; i <= n; i++) {
int x = arr[i - 1];
for (int s = 1; s <= half; s++) {
can[i][s] = can[i - 1][s]; // leave x out
if (x <= s && can[i - 1][s - x]) {
can[i][s] = true; // put x in
}
}
}
return can[n][half];
}
}How long it takes: one cell per (number, sum) pair, O(n * sum) where sum is half the total. Extra room: the table, O(n * sum).
Watch out for
- The table is only fast when the numbers are small. With numbers in the billions,
sumis huge and the table does not fit. Then the exponential way (or meet in the middle) is all you have. - In the one row version below, loop
sfrom high to low. Going low to high lets one number be used twice. - A single number can never be split, unless it is 0.
What interviewers ask next
- Can you use one row instead of a table? Yes. Keep
can[s]and for each number loopsfromhalfdown tox:can[s] = can[s] || can[s - x]. Extra room drops toO(sum). - Can you make it faster in practice? Store the row as bits and do
bits |= bits << xfor each number. That checks 64 sums per step. - Split into two groups with the smallest possible difference. Fill the same table for all sums up to
total / 2, then take the biggest reachables. The difference istotal - 2s. - Split into
kequal groups. That is much harder (LeetCode 698). Use backtracking with pruning, or DP over subsets with bitmasks.
Remember this
Grown-up words: the plate weight is the target sum, the yes or no table is the subset sum DP table, putting in or leaving out is the 0/1 knapsack choice, and the bit trick uses a bitset.