Problem overview
The function canPartition receives a list nums of positive integers and returns 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 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, 11, is odd and can't be split into two equal halves. For [1, 2, 5] it is also False: the total is 8, each group would need 4, and 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 once.
Approach: can some of the numbers make half the total?
If the total 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 others add up to the other half. So the question becomes: can some of the numbers make half?
Trying every possible group is far too slow: 200 numbers can be grouped in 2^200 ways. Instead keep a table of booleans, can, where 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: no numbers at all make 0.
Then add the numbers one at a time. For a number num, every sum that could be made before gives a new sum with num added, so can[s] becomes True when can[s - num] is. Run on [1, 5, 11, 5], where half is 11, the sums that are True after each number are:
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]
Sum 11 appears, so the answer is True. Each row builds on the one above: the second 5 adds 5 to every sum already reachable, which brings in the new sum 10 (5 + 5).
Why the sums go downward
The loop over the sums runs from half down to num, not upward. Going down, can[s - num] still holds the value from before this number, because the entries below s haven't been updated yet. So num is used at most once. Going up, can[s - num] may already include num, which lets one number be used again and again. On [1, 2, 5] with half = 4, the downward loop gives these reachable sums:
after 1 [0, 1]
after 2 [0, 1, 2, 3]
after 5 [0, 1, 2, 3]
Sum 4 is never reached, and the answer is False. The upward loop gives:
after 1 [0, 1, 2, 3, 4]
after 2 [0, 1, 2, 3, 4]
after 5 [0, 1, 2, 3, 4]
The single number 1 has already made 4, as 1 + 1 + 1 + 1, so the upward loop answers True wrongly. Problems that allow unlimited use of each value, such as counting coin combinations, go upward on purpose. This problem allows one use per number, so it goes downward.
Step by step
- Add up the numbers:
total = sum(nums). - Reject an odd total. If
total % 2 == 1, returnFalse. Two equal halves always add up to an even number. - Compute
halfastotal // 2. The total is even here, so the division is exact. - Create
canas[False] * (half + 1), one entry for each sum from 0 tohalf. No sum above 0 can be made before any number is added. - Set
can[0] = True. The empty group makes 0, and every other sum grows from it. - Loop over
numswith a variablenum, adding the numbers one at a time. After the loop has handled a number,canknows every sum the numbers so far can make, each number used at most once. - Loop
soverrange(half, num - 1, -1): every sum fromhalfdown tonum. A step of -1 makesrangecount down, and it stops beforenum - 1, sonumis the last sum visited. A sum belownumcan't containnum. - Update the table:
can[s] = can[s] or can[s - num]. The sumsis reachable if it already was, or if addingnumto a reachable sums - nummakes it. - Return
can[half]after both loops.
The solution in Python
class Solution:
def canPartition(self, nums: List[int]) -> bool:
total = sum(nums)
if total % 2 == 1:
return False
half = total // 2
can = [False] * (half + 1)
can[0] = True
for num in nums:
for s in range(half, num - 1, -1):
can[s] = can[s] or can[s - num]
return can[half]
The Python details worth a closer look:
- Counting down with
range.range(half, num - 1, -1)includeshalfand excludesnum - 1. Withhalf = 5andnum = 3it yields 5, 4, 3. Whennumis bigger thanhalf, the range is empty:list(range(4, 12, -1))is[], so a number that is too big is ignored. orreturns an operand.can[s] or can[s - num]evaluates to the first operand if it is true, and to the second otherwise. With booleans the result is a boolean, so the table staysTrue/False.//is floor division. It returns anint, which is what[False] * (half + 1)needs. A plain/gives a float such as11.0, and multiplying a list by a float raises aTypeError.- Mutating while reading. The loop updates
canin place and reads from the same list. That is safe only because of the downward order; a copy of the table per number would let any order work, at the cost of more memory.
Complexity
Time: O(n · sum). Each of the n numbers goes over at most half + 1 sums, with constant work for each. With the constraints (up to 200 numbers of up to 100), half is at most 10,000, so there are at most about two million table updates.
Space: O(sum). The table holds half + 1 booleans.
Edge cases and common mistakes
- An odd total.
[1, 2, 3, 5]and a single number such as[1]returnFalsewithout building the table. - A single number.
[100]has an even total, buthalfis 50 and the 100 is skipped by the empty range, socan[50]staysFalse. The answer isFalse, as it should be: one number can't be split. - Two equal numbers.
[2, 2]and[1, 1]returnTrue. - Even total, no split.
[1, 2, 5]has total 8 and returnsFalse: an even total is necessary, not sufficient. - Looping upward. It lets a number be used several times, so
[1, 2, 5]wrongly returnsTrue, as the tables above show. - Stopping the range at
num. Writingrange(half, num, -1)leaves outs == num, so a number could never make its own value.[3, 3]would then fail to reach 3. - Forgetting
can[0] = True. Every entry then staysFalseand the answer is alwaysFalse. - Large inputs. 200 numbers of 100 each (total 20,000) return
Truein a fraction of a second.
Testing
[1, 5, 11, 5]givesTrue.[1, 2, 3, 5]givesFalse.[1, 2, 5]givesFalse.[2, 2]givesTrue.[100]givesFalse.[3, 3, 3, 3]givesTrue.[1, 2, 3, 4, 5, 6, 7]givesTrue.
