Start learning free

LESSON 474 OF 573 · DYNAMIC PROGRAMMING

Partition Equal Subset Sum in TypeScript: O(n · sum) Dynamic Programming

Practice O(n · sum) Read the full explanation

Given an array 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

    TYPESCRIPT · DYNAMIC PROGRAMMING

    Partition Equal Subset Sum in TypeScript, explained

    Problem overview

    The method canPartition receives nums, an array 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. 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, an odd number that can't be split into two equal halves. For [1, 2, 5] it is also false: the total is 8, so each group would have to reach 4, but the numbers can only make 1, 2, 3, 5, 6, 7 and 8. The array has up to 200 numbers, each from 1 to 100.

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

    Approach: can some numbers make exactly half?

    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 rest add up to the other half. So the question becomes: can a selection of the numbers, each used at most once, make 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. A new number num turns every sum that could be made before into a new sum with num added. So can[s] becomes true when can[s - num] was true. This is dynamic programming: each answer is built from the answers for smaller sums.

    For [1, 5, 11, 5] the reachable sums, after each number, are:

    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 it is reachable, so the answer is true.

    Why the sums go down

    The inner loop goes from half down to num. Going down, can[s - num] has not been touched yet for this number, so it still describes the numbers before it, and num is used at most once. Going up would let can[s - num] already include num, which lets a number be used again and again. With the loop going up, [1, 2, 5] would reach 4 as 1 + 1 + 1 + 1 and return true, while the correct answer is false.

    Step by step

    1. Add up the numbers into total with nums.reduce((sum, num) => sum + num, 0). It starts from 0 and adds each number to the running sum.
    2. Reject an odd total: if total % 2 === 1, return false. Two equal halves always add up to an even number.
    3. Compute half as total / 2. The total is even here, so nothing is lost in the division.
    4. Create can, half + 1 values, all false, with new Array(half + 1).fill(false).
    5. Set can[0] to true. Every sum grows from the empty selection.
    6. Loop over the numbers with for...of.
    7. Loop over the sums with let s = half while s >= num, going down. A sum smaller than num can't contain it.
    8. Update: can[s] = can[s] || can[s - num]. The sum s is reachable if it already was, or if s - num was reachable before this number.
    9. Return can[half].

    The solution in TypeScript

    class Solution {
        canPartition(nums: number[]): boolean {
            const total: number = nums.reduce((sum, num) => sum + num, 0);
            if (total % 2 === 1) {
                return false;
            }
            const half: number = total / 2;
            const can: boolean[] = new Array(half + 1).fill(false);
            can[0] = true;
            for (const num of nums) {
                for (let s = half; s >= num; s--) {
                    can[s] = can[s] || can[s - num];
                }
            }
            return can[half];
        }
    }
    

    TypeScript details:

    • reduce with an initial value. The 0 is the starting sum. The result is a number, and for an array with one element, such as [7], it gives 7.
    • Numbers are doubles. total / 2 is exact when total is even, and total % 2 is 0 or 1 for positive totals. The values here are far below 2^53, so nothing loses precision.
    • new Array(n).fill(false). new Array(half + 1) makes an array with that many empty slots, and fill(false) sets each one to false. The annotation boolean[] is only for the compiler.
    • || short-circuits. If can[s] is already true, the right side is not evaluated.
    • The loop variable. s is declared with let because it counts down. num is a const that gets a new value on every pass of for...of.

    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. The largest input, 200 numbers of 100, has a half of 10,000 and finishes with about two million updates.

    Space: O(sum). The table can holds half + 1 booleans.

    Edge cases and common mistakes

    • Odd total. [1, 2, 3, 5] and [1] return false before the table is even built.
    • Two equal numbers. [2, 2] and [1, 1] return true, one number per group.
    • One number. [100] returns false: the total is even, but a single number can't fill two groups. A number larger than half never enters the inner loop, because s >= num is false from the start.
    • Even total, no split. [1, 2, 5] has total 8 and returns false, and so does [2, 2, 3, 5] with total 12. An even total is necessary, not sufficient.
    • A split exists. [3, 3, 3, 4, 5] and [1, 2, 3, 4, 5, 6, 7] both return true.
    • Looping up instead of down. It reuses numbers, as shown above for [1, 2, 5].
    • Table of the wrong size. can[half] needs half + 1 entries, because indexes run from 0 to half.
    • Forgetting can[0] = true. Without it, nothing ever becomes true, and the answer is always false.
    • Largest input. 200 numbers equal to 100 return true (100 per group). Changing one of them to 99 makes the total odd, and the answer is false at once.

    Testing

    • [1, 5, 11, 5] gives true.
    • [1, 2, 3, 5] gives false.
    • [1, 2, 5] gives false.
    • [2, 2] gives true.
    • [1] gives false.
    • [100] gives false.
    • [3, 3, 3, 4, 5] gives true.
    • [2, 2, 3, 5] gives false.
    • [1, 2, 3, 4, 5, 6, 7] gives true.
    Keep going with TypeScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free