Problem overview
The method canPartition receives nums, an Array[Int] of positive integers, and returns a Boolean: true when the numbers can be split into two groups with equal sums, and false otherwise. Every number goes into exactly one group, and repeated values are separate numbers, each copy going into one group.
For [1, 5, 11, 5] the answer is true: [1, 5, 5] and [11] both add up to 11. For [1, 2, 3, 5] it is false, because the total, 11, is odd. For [1, 2, 5] it is false too: the total is 8, so each group would need 4, but no choice of the numbers makes 4.
The lesson expects O(n * sum) time and O(sum) extra space, where n is the length of nums and sum is its total. With up to 200 numbers, trying every way to form the groups, 2^200 of them, is out of the question.
Approach: can some numbers make half the total?
Two equal groups can only exist when the total is even. If it is odd, the answer is false at once. Otherwise let half be the total divided by 2. If some of the numbers add up to exactly half, the remaining numbers add up to the other half. So the question becomes: can a subset of nums make the sum half?
That is answered with dynamic programming. Keep a table of booleans, can(s), which 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. For a number num, every sum that was possible before gives a new sum with num added: can(s) becomes true when can(s - num) is true. After all numbers have been handled, can(half) is the answer.
Here is the table of reachable sums for [1, 5, 11, 5], with half = 11:
| After | Sums that can be made (up to 11) |
|---|---|
| no numbers | 0 |
| 1 | 0, 1 |
| 5 | 0, 1, 5, 6 |
| 11 | 0, 1, 5, 6, 11 |
| 5 | 0, 1, 5, 6, 10, 11 |
The sum 11 is reachable, so the answer is true. It was already reachable after the 11, as the 11 alone.
Step by step
- Add everything up into a
val totalwithnums.sum. - Reject odd totals. If
total % 2 == 1, returnfalse. Two equal halves always add up to an even number. - Compute
halfastotal / 2. - Create the table
canasnew Array[Boolean](half + 1). Indexes go from 0 tohalf, and a newArray[Boolean]starts allfalse. - Set
can(0)totrue. Every sum grows from this one: a number added to no numbers makes that number's own value. - Loop over the numbers with
num <- nums. - Loop over the sums downwards with
s <- half to num by -1. A sum smaller thannumcan't contain it, so the range stops atnum. Whennumis bigger thanhalf, the range is empty and the number is skipped. - Update the table:
can(s) = can(s) || can(s - num). If some earlier numbers makes - num, addingnumto them makess. - Return
can(half)as the last expression.
The solution in Scala
class Solution {
def canPartition(nums: Array[Int]): Boolean = {
val total = nums.sum
if (total % 2 == 1) {
return false
}
val half = total / 2
val can = new Array[Boolean](half + 1)
can(0) = true
for (num <- nums) {
for (s <- half to num by -1) {
can(s) = can(s) || can(s - num)
}
}
can(half)
}
}
Scala details worth a look:
- Why the sums go down. Going from
halftonum, the entrycan(s - num)has not been changed yet for this number, so it still describes the numbers before it, and each number is used at most once. Going up would let one number feed itself: withfor (s <- num to half)the answer for[1, 2, 5]becomestrue, because 4 is made as 1 + 1 + 1 + 1 or 2 + 2. half to num by -1is a range that counts down and includes both ends. Withoutby -1, the rangehalf to numis empty whenevernumis smaller thanhalf.nums.sumadds the elements of the array. The sum is anInt, which is plenty here: at most 200 numbers of at most 100 give 20,000.%and/onIntgive the remainder and the quotient that truncates. The check is made for an odd total, so the division by 2 is exact afterwards.- The shape of the table.
new Array[Boolean](half + 1)has indexes0tohalf, socan(half)is valid. The table is a one-dimensional array that is updated in place; this is why the loop direction matters. ||short-circuits:can(s - num)is only read whencan(s)is still false.
Complexity
Time: O(n * sum). Each of the n numbers goes over at most half + 1 sums, with constant work for each sum.
Space: O(sum). The table holds half + 1 booleans.
Edge cases and common mistakes
- Odd total.
[1, 2, 3, 5]returnsfalsebefore the table is built. - A single number.
[2]and[100]both returnfalse. For[100]the total is even andhalfis 50, but the number is bigger thanhalf, so its range is empty andcan(50)stays false. - Two equal numbers.
[3, 3]and[1, 1]returntrue. - Looping upwards. It reuses a number, as described above. On
[1, 2, 5]it returnstrue, while the correct answer isfalse. - A bigger input. 200 copies of 100 return
true, and 199 copies of 100 plus one 99 (an odd total) returnfalse.
Testing
[1, 5, 11, 5]givestrue.[1, 2, 3, 5]givesfalse.[1, 2, 5]givesfalse.[3, 3]givestrue.[3, 3, 3, 4, 5]givestrue.[2]givesfalse.
