Problem overview
The method CanPartition receives an int[] named nums of positive integers and returns a bool: can the numbers be split into two groups with equal sums? Every number goes into exactly one group, and repeated values are separate numbers.
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 cut into two equal halves. For [1, 2, 5] it is also false: the total 8 asks for two groups of 4, and the numbers can only make 1, 2, 3, 5, 6, 7 and 8, never 4.
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 half, the rest add up to half too, so the question becomes: can a selection of the numbers make exactly half?
Trying every selection is hopeless: 200 numbers can be grouped in 2^200 ways. Instead 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 could be made before gives a new sum with num added: can[s] becomes true if can[s - num] was true. This is dynamic programming: each entry reuses answers for smaller sums. Here is the table for [1, 5, 11, 5], with the sums that are true after each number:
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 half is 11, and 11 is true after the third number, so the answer is true.
Why the sums go down
The inner loop goes from half down to num. Going down, can[s - num] still holds its value from before this number, because smaller indexes are updated later. So each number is used at most once. Going up would let the same number feed itself: after can[1] becomes true, can[2] would use it, and then can[3], and so on, so a number could be used many times. For [1, 2, 5] the upward version returns true, using 1 + 1 + 1 + 1 or 2 + 2 to make 4, while the correct answer is false. The table with the downward loop shows the right behaviour:
start 0
after 1 0 1
after 2 0 1 2 3
after 5 0 1 2 3
Half of 8 is 4, and 4 never appears.
Step by step
- Add up the numbers into an
intnamedtotalwithnums.Sum(). - Reject odd totals. If
total % 2 == 1, returnfalse. - Compute
half = total / 2. - Create
can, abool[]of lengthhalf + 1. A newboolarray is allfalse. - Set
can[0] = true: no numbers make the sum 0, and every other sum grows from it. - Loop over the numbers with
foreach. - Loop over the sums downward:
sfromhalfwhiles >= num, because a sum smaller thannumcan't contain it. - Update:
can[s] = can[s] || can[s - num]. A sum stays reachable if it already was, and becomes reachable ifs - numwas. - Return
can[half].
The solution in C#
class Solution {
public bool CanPartition(int[] nums) {
int total = nums.Sum();
if (total % 2 == 1) {
return false;
}
int half = total / 2;
bool[] can = new bool[half + 1];
can[0] = true;
foreach (int num in nums) {
for (int s = half; s >= num; s--) {
can[s] = can[s] || can[s - num];
}
}
return can[half];
}
}
The C# details worth knowing:
nums.Sum()is a LINQ extension method fromSystem.Linq. In a file of your own, addusing System.Linq;at the top. The method adds the elements asint.- The remainder operator.
total % 2 == 1is a correct test for an odd total here because the numbers are positive, so the total is positive. For negative values%can give-1, sototal % 2 != 0is the safer habit. - Integer division.
total / 2truncates, and the odd case was already rejected, so it is exact. - Short-circuit
||. Incan[s] || can[s - num], the right side is only read when the left side isfalse. Both indexes are always inside the array:s <= halfands - num >= 0by the loop condition.
Complexity
Time: O(n * sum) where n is the number of values and sum is their total. Each of the n numbers goes over at most half + 1 sums, with constant work for each.
Space: O(sum). The can array holds half + 1 booleans.
Edge cases and common mistakes
- Odd total.
[3]and[1, 2, 3, 5]returnfalsebefore the table is built. - One number.
[2]has an even total, buthalfis 1 and 1 can't be made, so the answer isfalse. - Two equal numbers.
[1, 1]returnstrue. - A number bigger than half. In
[1, 2, 9]the total is 12 andhalfis 6. The inner loop never runs for 9, so it is never used, and the answer isfalse. - Looping upward. It reuses numbers and gives
truefor[1, 2, 5]. - Looping down to 0. The condition
s >= nummatters; withs >= 0instead,can[s - num]reads a negative index whens < numandcan[s]is still false, and that throws anIndexOutOfRangeException. (Whencan[s]is already true,||skips the read, so some inputs, such as{1, 1}, still run.) - Forgetting
can[0] = true. Every entry staysfalse, and the method always returnsfalse.
Testing
[1, 5, 11, 5]givestrue.[1, 2, 3, 5]givesfalse.[1, 2, 5]givesfalse.[1, 1]givestrue.[2]and[3]givefalse.[1, 2, 9]givesfalse.[1, 2, 3, 4, 5, 6, 7]givestrue.- Two hundred copies of 100 give
true.
