Problem overview
The function can_partition receives nums, a vector of positive integers, and returns true when its numbers can be split into two groups with equal sums, or false otherwise. Every number goes into exactly one group, and repeated values are separate numbers: each copy goes into one group.
For [1, 5, 11, 5] the answer is true, because [1, 5, 5] and [11] both add up to 11. For [1, 2, 3, 5] the total is 11. That is odd, so no split can be equal, and the answer is false. For [1, 2, 5] the total is 8, so each group would need 4, but no group of the numbers adds up to 4, and the answer is false.
The statement sets O(n * sum) time and O(sum) extra space, where n is the number of values and sum is their total.
Approach: can some of the numbers make half?
If the total is odd, the answer is false: two equal halves always add up to an even number. Otherwise the question becomes a smaller one. Let half be the total divided by 2. If some of the numbers add up to exactly half, the rest add up to the other half. So the task is to decide whether a group of the numbers makes half.
Trying every group is far too slow: 200 numbers can be grouped in 2^200 ways. Dynamic programming avoids that with a table of booleans: can[s] says whether some of the numbers seen so far add up to s, for every s from 0 to half. Before any number is seen, only can[0] is true, because no numbers at all make 0.
Then add the numbers one at a time. A new number num turns every sum that could be made before into a new sum: if can[s - num] is true, then adding num makes can[s] true. After the last number, can[half] is the answer.
Here is the table for [1, 5, 11, 5], written as the list of sums that are true:
total = 22, half = 11, start: sums = [0]
after 1: sums = [0, 1]
after 5: sums = [0, 1, 5, 6]
after 11: sums = [0, 1, 5, 6, 11]
after 5: sums = [0, 1, 5, 6, 10, 11]
can[11] = true
And for [1, 2, 5], where the total is 8 and half is 4:
total = 8, half = 4, start: sums = [0]
after 1: sums = [0, 1]
after 2: sums = [0, 1, 2, 3]
after 5: sums = [0, 1, 2, 3]
can[4] = false
The 5 changes nothing, because it is bigger than half, and 4 is never reached.
Why the sums are visited downward
For each number, the inner loop goes over the sums from half down to num. Going down, can[s - num] still holds the value from before this number, because the entries below s have not been updated yet. So each number is used at most once.
Going up would break that. Then can[s - num] may already include the same number, which lets it be added again and again. On [1, 2, 5] an upward loop makes 4 as 1 + 1 + 1 + 1, and the function wrongly returns true. On [100, 1, 1], with total 102 and half 51, it also returns true, by adding the 1 fifty-one times. The correct answer for both is false.
Step by step
- Add the numbers up into
total, ani32, withnums.iter().sum(). The type ontotaltellssum()what kind of number to make. - Reject an odd total. If
total % 2 == 1, returnfalseat once. - Compute
halfas(total / 2) as usize. It is used as an index, and indexes areusizein Rust. - Create the table
canasvec![false; half + 1], one entry for each sum from 0 tohalf. Nothing above 0 can be made yet. - Set
can[0]totrue. Every group grows from this one: one number added to no numbers makes that number's own value. - Loop over
&numswith the pattern&num, which borrows the vector and copies each value out of it. - Loop over the sums with
(num as usize..=half).rev(): fromhalfdown tonum. A sum smaller thannumcannot contain it. Whennumis bigger thanhalf, the range is empty and the number is ignored. - Update the table:
can[s] = can[s] || can[s - num as usize]. A sum stays reachable if it was, and becomes reachable ifs - numwas. - Return
can[half]as the last expression.
The solution in Rust
impl Solution {
fn can_partition(nums: Vec<i32>) -> bool {
let total: i32 = nums.iter().sum();
if total % 2 == 1 {
return false;
}
let half = (total / 2) as usize;
let mut can = vec![false; half + 1];
can[0] = true;
for &num in &nums {
for s in (num as usize..=half).rev() {
can[s] = can[s] || can[s - num as usize];
}
}
can[half]
}
}
The Rust details worth a closer look:
sum()needs a type.nums.iter().sum()could produce several number types, solet total: i32tells it which. With at most 200 numbers of at most 100, the total is at most 20000, far insidei32.as usizefor indexes. Vectors are indexed byusize. The values here are positive, so the casts are exact. A negativei32would turn into a hugeusizeinstead of failing, which is why the problem's guarantee of positive numbers matters.- Precedence of
as. Innum as usize..=half, the cast is applied tonumbefore the range is built. Ins - num as usize, it applies tonumonly, and then the subtraction is between twousizevalues. (a..=b).rev(). The..=range includeshalf, andrev()walks it backwards. A range whose start is above its end yields nothing, so the loop body is skipped.- No underflow in
s - num. The loop only visitssfromnumupward, so the subtraction never goes below zero. for &num in &nums. Iterating&numsyields&i32, and the&in the pattern copies the integer out. The vector is borrowed, not consumed.- Short-circuit
||.can[s - num as usize]is only read whencan[s]is false. Both indexes are in range either way. - Final expression.
can[half]has no semicolon, so it is the returnedbool.
Complexity
Time: O(n * sum). Each of the n numbers goes over at most half + 1 sums, with constant work for each, and half is half of the total.
Space: O(sum). The table holds half + 1 booleans.
Edge cases and common mistakes
- Odd total.
[1]and[1, 2, 3, 5]returnfalsebefore any table is built. - A number bigger than
half. In[100, 1, 1], the 100 can never be in a group that makes 51, so its range is empty and it changes nothing. The 1s make at most 2, so the answer isfalse. - Looping upward. It reuses a number, as shown above. This is the most common bug in this lesson.
- Forgetting
can[0] = true. Nothing would ever become true, and every answer would befalse. - Table size. The table needs
half + 1entries, nothalf, so thatcan[half]exists. - Equal numbers.
[1, 1]and[2, 2]returntrue: each copy is its own number, used once. - Large input. With 200 numbers of 100, the total is 20000,
halfis 10000, and the table has 10001 entries. It is small and fast.
Testing
[1, 5, 11, 5]givestrue.[1, 2, 3, 5]givesfalse.[1, 2, 5]givesfalse.[100, 1, 1]givesfalse.[2, 2]givestrue.[3, 3, 3, 4, 5]givestrue.[1]givesfalse.
