Problem overview
The function canPartition receives a slice nums of positive integers and returns true if the numbers can be split into two groups with equal sums, and false otherwise. Every number goes into exactly one group. Repeated values are separate numbers, and each copy goes 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 is 11, which is odd and can't be split into two equal halves. For [1, 2, 5] it is also false: the total is 8, so each group would need 4, but the numbers can only make 1, 2, 3, 5, 6, 7 and 8. Using the 1 four times or the 2 twice would make 4, but each number can be used only once.
The statement asks for 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 the total?
If the total is odd, the answer is false straight away. Otherwise 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 question becomes: can some of the numbers make exactly half?
Trying every possible group is far too slow, since 200 numbers can be grouped in 2^200 ways. Instead, keep 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. For a number num, any sum that could be made before gives a new sum with num added, so can[s] becomes true when can[s - num] is true. The loop over the sums goes from half down to num. Going down, can[s - num] still holds its value from before this number, so each number is used at most once. A loop that went up would reuse the same number in one pass, as in coin problems where a coin can be used again and again.
The table on the first example
For [1, 5, 11, 5] the total is 22 and half is 11. Printing the sums marked true after each number gives:
start [0]
after 1 [0 1]
after 5 [0 1 5 6]
after 11 [0 1 5 6 11]
after 5 [0 1 5 6 10 11]
The last list contains 11, so the answer is true. For [1, 2, 5], the total is 8 and half is 4:
start [0]
after 1 [0 1]
after 2 [0 1 2 3]
after 5 [0 1 2 3]
The list never contains 4, so the answer is false. The 5 is bigger than half, so it can't change the table.
Step by step
- Add up the numbers. Start
totalat 0 and add eachnumin arangeloop. - Reject an odd total. If
total%2 == 1, returnfalse: two equal halves always add up to an even number. - Compute
halfastotal / 2. - Create
canwithmake([]bool, half+1). Every entry starts asfalse, and the extra place holds the sumhalfitself. - Set
can[0] = true. Every sum grows from the empty group. - Loop over
numsagain. After a number is handled,canknows every sum the numbers so far can make, each one used at most once. - Loop over the sums downward:
sfromhalfwhiles >= num. A sum belownumcan't contain it. - Update the table:
can[s] = can[s] || can[s-num]. If the earlier numbers can makes - num, addingnummakess. - Return
can[half].
The solution in Go
func canPartition(nums []int) bool {
total := 0
for _, num := range nums {
total += num
}
if total%2 == 1 {
return false
}
half := total / 2
can := make([]bool, half+1)
can[0] = true
for _, num := range nums {
for s := half; s >= num; s-- {
can[s] = can[s] || can[s-num]
}
}
return can[half]
}
The Go details worth a closer look:
rangeand the blank identifier.for _, num := range numsyields an index and a value. Go refuses to compile an unused variable, so the index is dropped with_.makefor a slice of bools.make([]bool, half+1)createshalf+1elements, allfalse, which is the zero value ofbool.||short-circuits. Ifcan[s]is alreadytrue, the right side is not evaluated. The result is the same either way, because both sides are plain reads.- Integer division.
total / 2truncates, andtotal%2is the remainder. With the odd total ruled out first,halfis exact. For an odd total such as 11,11 / 2is 5 and11 % 2is 1. - A loop that may not run. If
numis larger thanhalf, the conditions >= numis false at once, so the inner loop does nothing and the indexs-numis never negative.
Complexity
Time: O(n · sum). Each of the n numbers goes over at most half + 1 sums, with a constant amount of work for each one.
Space: O(sum). The table can holds half + 1 booleans.
Edge cases and common mistakes
- Odd total.
[1]and[1, 2, 3, 5]returnfalsebefore any table is built. - A single number.
[2]has an even total, but the half is 1 and no number makes it, so the answer isfalse. - Equal pairs.
[3, 3]and[1, 1]returntrue, since each copy goes into one group. - One number is too big.
[1, 1, 6]has a total of 8 and a half of 4. The 6 can't fit in a group of 4, and the answer isfalse. The same happens for[100, 1, 1, 1, 1]. - Looping upward. If the inner loop runs
for s := num; s <= half; s++, each number can be used again in the same pass. For[1, 2, 5]that version returnstrue, because 4 is built as 1 + 1 + 1 + 1, which is wrong; the correct answer isfalse. - Large inputs. 200 numbers equal to 100 give a total of 20,000 and a table of 10,001 entries, and the answer
truecomes back at once. Changing one of the 100s to 99 makes the total odd, and the answer isfalsebefore the table is built.
Testing
[1, 5, 11, 5]givestrue.[1, 2, 3, 5]givesfalse.[1, 2, 5]givesfalse.[3, 3]givestrue.[2]givesfalse.[4, 4, 4, 4, 4, 4]givestrue.
