Start learning free

LESSON 469 OF 568 · DYNAMIC PROGRAMMING

Partition Equal Subset Sum in C++: 1D Dynamic Programming

Practice O(n · sum) Read the full explanation

Given a vector of positive integers nums, return true if its numbers can be split into two groups with equal sums, or false otherwise.

Every number goes into exactly one of the two groups. Numbers that repeat in nums are separate numbers: each copy goes into one group.

 

Example 1:

Input: nums = [1, 5, 11, 5]
Output: true
Explanation: [1, 5, 5] and [11] both add up to 11.

Example 2:

Input: nums = [1, 2, 3, 5]
Output: false
Explanation: The total is 11. It is odd, so it can't be split into two equal halves.

Example 3:

Input: nums = [1, 2, 5]
Output: false
Explanation: The total is 8, so each group would have to add up to 4. But the numbers
can only make 1, 2, 3 (1 + 2), 5, 6 (1 + 5), 7 (2 + 5) and 8: never 4. Using the 1 four times
or the 2 twice would make 4, but each number can be used only once.

 

Constraints:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

Note:

Your algorithm should run in O(n · sum) time and use O(sum) extra space, where n is the number of values in nums and sum is their total.



If the total is odd, the answer is false. Otherwise the question becomes: can some of the numbers add up to exactly half, the total divided by 2? The other numbers then make the other half. Trying every group is far too slow: 200 numbers can be grouped in 2200 ways. Keep a table of booleans instead: 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.

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 when can[s - num] is. Go over the sums from half down to num. Going down, can[s - num] is still the value from before this number, so each number is used at most once.
Why down, when Coin Change II goes up: in Coin Change II the loop over the sums goes up on purpose. There ways[a - coin] may already count the same coin, and that is how one coin value is used again and again. Here going up would reuse a number: on [1, 2, 5] it would make 4 as 1 + 1 + 1 + 1 or 2 + 2, and wrongly answer true for Example 3.

Each of the n numbers goes over at most half + 1 sums, with a constant amount of work for each: O(n · sum) time. The table holds half + 1 booleans: O(sum) space.
STEP 1 OF 9
    Ctrl + Enter to check

    C++ · DYNAMIC PROGRAMMING

    Partition Equal Subset Sum in C++, explained

    Problem overview

    The method canPartition receives a vector<int> named nums of positive integers and returns true when the numbers can be split into two groups with equal sums, 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] the total is 11, which is odd, so no split is possible. For [1, 2, 5] the total is 8, so each group would need 4, but the numbers can only make 1, 2, 3, 5, 6, 7 and 8, never 4, so the answer is false.

    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: which sums can the numbers make?

    If the total is odd, the answer is false, since two equal halves always add up to an even number. Otherwise let half be the total divided by 2. If some of the numbers add up to exactly half, the rest make the other half, so the question becomes: can some of the numbers add up to 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: 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] was already true. For [1, 5, 11, 5] the sums that can be made grow like this (half is 11):

    AfterSums that can be made
    no numbers0
    10, 1
    50, 1, 5, 6
    110, 1, 5, 6, 11
    50, 1, 5, 6, 10, 11

    The table contains 11 at the end, so the answer is true. For [1, 2, 5], whose half is 4, the sums are 0, 1, 2 and 3 after the numbers 1 and 2, and still 0, 1, 2 and 3 after the 5, which is too big to matter. 4 is never reached, and the answer is false.

    The order of the inner loop matters. The sums are visited 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. Going up, can[s - num] may already include num itself, and the number would be reused. On [1, 2, 5] an upward loop makes 4 as 1 + 1 + 1 + 1 and wrongly answers true.

    Step by step

    1. Add everything up. accumulate(nums.begin(), nums.end(), 0) adds every number to the starting value 0 and stores the result in total.
    2. Return false for an odd total, checked with total % 2 == 1.
    3. Compute half as total / 2. The target is now to make exactly this sum from some of the numbers.
    4. Create the table. vector<bool> can(half + 1, false) has one entry for each sum from 0 to half, all false. The constructor takes the size first and the value for every element second.
    5. Set can[0] to true. The empty group makes 0, and every other sum grows from it.
    6. Loop over the numbers with a range-based for. After the loop has handled a number, the table knows every sum the numbers so far can make, each used at most once.
    7. Loop over the sums downward, with s from half while s >= num. A sum smaller than num can't contain it.
    8. Update the entry: can[s] = can[s] || can[s - num]. Either the sum was already possible, or it is possible by adding num to a smaller sum that was.
    9. Return can[half], after the outer loop.

    The solution in C++

    class Solution {
    public:
        bool canPartition(vector<int>& nums) {
            int total = accumulate(nums.begin(), nums.end(), 0);
            if (total % 2 == 1) {
                return false;
            }
            int half = total / 2;
            vector<bool> can(half + 1, false);
            can[0] = true;
            for (int num : nums) {
                for (int s = half; s >= num; s--) {
                    can[s] = can[s] || can[s - num];
                }
            }
            return can[half];
        }
    };
    

    The C++ details worth a closer look:

    • accumulate and its starting value. The third argument, 0, is an int, so the sum is computed as an int. That is safe here: at most 200 numbers of at most 100 give 20,000.
    • vector<bool>. It is a space-saving special case of vector that stores one bit per element. For this use it reads and writes like any other vector.
    • || short-circuits. If can[s] is already true, the right side isn't evaluated. Either way the result is the same.
    • The loop condition s >= num. It keeps s - num from going below 0, so the index is always valid. The variables are int, so the countdown is safe, unlike a loop with an unsigned counter.
    • nums is a non-const reference, which the lesson's signature requires. The method only reads it.

    Complexity

    Time: O(n * sum). Each of the n numbers walks over at most half + 1 sums, with a constant amount of work for each.

    Space: O(sum). The table holds half + 1 booleans. Reusing a single row of the table, instead of a new row for each number, is what keeps the space this small, and it only works because the sums are visited downward.

    Edge cases and common mistakes

    • Looping upward. Reusing a number: the answer for [1, 2, 5] flips from false to true.
    • Skipping the odd-total check. Integer division makes total / 2 round down, so an odd total would be tested against a wrong target. Without the check, [1, 2, 3, 5] (total 11, half 5) answers true because 2 + 3 = 5, and [1] answers true because its half is 0.
    • A single number. [1] has an odd total and gives false. The input always has at least one number, but one number can never be split into two equal groups.
    • Two equal numbers. [1, 1] and [2, 2] give true: each copy goes into its own group.
    • Forgetting can[0] = true. Then every entry stays false forever, since every sum is built from smaller ones that started at 0.
    • A large number. If a number is bigger than half, the inner loop doesn't run for it, and that number can't be in the group that makes half. It is part of the other group. For [1, 5, 11, 5], the 11 equals half, so it makes the sum 11 alone.
    • Largest input. 200 numbers equal to 100 give a total of 20,000 and a table of 10,001 entries. The answer is true.

    Testing

    • [1, 5, 11, 5] gives true.
    • [1, 2, 3, 5] gives false.
    • [1, 2, 5] gives false.
    • [1] gives false.
    • [3, 3, 3, 3] gives true.
    • [2, 3, 4, 5, 6] gives true: the total is 20, and 4 + 6 = 10.
    • [1, 2, 3, 4, 5, 6, 7] gives true.
    Keep going with C++500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free