Algorithm

Partition Subset Sum

Dynamic Programming Pattern

Partition Equal Subset Sum

You are given an array nums of positive integers. Decide whether the array can be split into two groups such that every element belongs to exactly one group and the two groups have the same sum. Return true if such a split exists and false otherwise. You only need to report whether it is possible, not produce the groups.

CONSTRAINTS
  • 1 ≤ nums.length ≤ 200
  • 1 ≤ nums[i] ≤ 100
  • Every element must land in exactly one of the two groups
  • The groups do not need to be the same size, only the same sum
EXAMPLE 1
Input: nums = [1, 5, 11, 5]
Output: true
The whole array sums to 22, so each side must reach 11. The group {11} and the group {1, 5, 5} both do.
EXAMPLE 2
Input: nums = [1, 2, 3, 5]
Output: false
The total is 11, an odd number. No two whole-number sums can each be half of an odd total, so no split exists regardless of how the elements are arranged.
EXAMPLE 3
Input: nums = [2, 2, 3, 5]
Output: false
The total 12 is even, so each side would need 6 — but no combination of 2, 2, 3 and 5 adds up to exactly 6. An even total is necessary but not sufficient.
EXAMPLE 4
Input: nums = [1, 1]
Output: true
One element in each group, each summing to 1. The groups do not have to be interesting, only equal.
EXAMPLE 5
Input: nums = [7]
Output: false
With a single element, one group gets 7 and the other gets nothing. Every element must be placed, so an empty group is only acceptable if the other also sums to zero.
Must both groups be non-empty, and must they be the same size?
They must have equal sums, not equal sizes — {11} against {1, 5, 5} is a valid answer. Since all values are positive, an equal split automatically forces both groups to be non-empty, so it rarely needs separate handling.
Can the numbers be zero or negative?
Here they are strictly positive, and that matters more than it looks. Positivity is what makes 'overshooting the target is pointless' a safe rule. With negatives allowed, a sum could dip above the target and come back down, and this approach would need reworking.
Do I have to return the actual two groups?
No, just true or false. If the groups were required, you would keep the full two-dimensional table rather than the single row and walk backwards from the final cell, reading off at each step whether the take branch or the skip branch produced the value.
How large can the total sum get?
With 200 elements capped at 100 each, the total is at most 20000 and the target at most 10000. That bound is what makes a table indexed by the target practical, so it is worth asking about explicitly — if values could be up to a billion, this technique would be dead and the problem would need a different idea entirely.
Turning the question into a smaller one

"Split into two equal halves" sounds like it needs two decisions per element — which group does it go in? It does not. Watch.

Let total be the sum of everything. If the two groups are to be equal, each must sum to total / 2. And here is the collapse: once you have chosen one group, the other group is whatever is left over, and its sum is forced. So you never need to think about the second group at all. The question becomes:

Is there some subset of nums whose sum is exactly total / 2?

That reduction buys us two things immediately. First, an instant rejection: if total is odd, there is no such thing as half of it in whole numbers, and the answer is false before we do anything. On [1, 2, 3, 5] the total is 11, so no split can exist — done in one line. Second, it turns a vague partitioning question into a crisp target-hitting question, and target-hitting questions have a standard shape.

Call the target T = total / 2.

The honest brute force

Every element is either in the chosen subset or not. With n elements that is 2ⁿ possible subsets, and we could sum each one and check it against T. For n = 200 that is a number with sixty digits. Not viable, but it is the correct starting point, because it names the decision: for each element, take it or leave it.

The state, as a story

I go through the elements one at a time, from the last toward the first. Standing at element i with a target t still to reach, I have two choices:

- Take it (only if nums[i] <= t) — I put nums[i] in my group, so my target drops: f(i-1, t - nums[i]).
- Leave it — my target stays the same: f(i-1, t).

I only need one of the two to work, so I join them with or. The two things I must track are which element I'm on (i) and how much target is left (t).

State Definitionf(i, t) is true if I can make a sum of exactly t using some of the elements from index 0 through i.

f(i, t) = f(i-1, t) or ( t >= nums[i] and f(i-1, t - nums[i]) )

(This is the yes/no flavour: a counting version would use +, a best-value version max.)

Base cases, two of them:

- If t == 0, the target is already met — return true. Check this one first.
- If i == 0, only the first element is left. It can only add nums[0], so the answer is whether nums[0] equals the remaining target: nums[0] == t.

We start the whole thing by asking about the last element with the full target: f(n-1, T).

Recursion, then the notebook
python
def can_partition(nums):
    total = sum(nums)
    if total % 2 != 0: return False
    T = total // 2
    n = len(nums)
    memo = {}

    def f(i, t):
        if t == 0: return True
        if i == 0: return nums[0] == t
        if (i, t) in memo: return memo[(i, t)]
        skip = f(i - 1, t)
        take = t >= nums[i] and f(i - 1, t - nums[i])
        memo[(i, t)] = take or skip
        return memo[(i, t)]

    return f(n - 1, T)

How fast is this now? Each state (i, t) is worked out once and then read back from the notebook. There are n choices for i and T+1 choices for t, so at most n × (T+1) states in all. With n ≤ 200 and T ≤ 10000, that is about two million small steps — fast.

The rule to remember: a memoised solution's speed is simply how many different states there are, because each state is solved only once.

Building the table without recursion

Turn the recursion into a table the same mechanical way. Let dp[i][t] hold exactly what f(i, t) returned: true if we can make the sum t using elements from index 0 through i.

Now read the recurrence as an assignment. It said

f(i, t) = f(i-1, t) or ( t >= nums[i] and f(i-1, t - nums[i]) )

so, swapping each call for an array read, the very same line becomes

dp[i][t] = dp[i-1][t] or ( t >= nums[i] and dp[i-1][t - nums[i]] )

It is the identical formula. We just store each answer in a cell instead of returning it.

Fill the rows top to bottom (i = 0, 1, 2, ...), so each row i-1 is ready before row i uses it.

The base cases just get copied over from the recursion. Read each base line and write down the cell it describes:

- Recursion said if t == 0: return true. So set column t = 0 true in every row: dp[i][0] = True.
- Recursion said if i == 0: return nums[0] == t. So in row 0, set only dp[0][nums[0]] = True; the rest of row 0 stays false.

Then the loop starts at row i = 1, and the answer is dp[n-1][T]:

python
def can_partition(nums):
    total = sum(nums)
    if total % 2 != 0: return False
    T = total // 2
    n = len(nums)

    # dp[i][t] = can we make t using elements from index 0 through i?
    dp = [[False] * (T + 1) for _ in range(n)]
    for i in range(n):
        dp[i][0] = True                 # target 0: take nothing
    if nums[0] <= T:
        dp[0][nums[0]] = True           # first element alone can only make its own value

    for i in range(1, n):
        for t in range(1, T + 1):
            skip = dp[i - 1][t]
            take = dp[i - 1][t - nums[i]] if t >= nums[i] else False
            dp[i][t] = take or skip

    return dp[n - 1][T]
Collapsing to a single row

Row i reads only from row i-1, the elements before it. Nothing further back is ever consulted. So one row is enough — as long as we are careful about the order we overwrite it in.

Here is the trap. If we keep one array dp and sweep t upward, then when we compute dp[t] we read dp[t - nums[i]], which is a smaller index — and on this sweep we have already overwritten it with the new row's value. That means we would use element i once and then immediately use it again. That is a different problem, where each element may be used any number of times. To keep each element usable once, sweep t downward, so every value we read still comes from the previous row:

python
def can_partition(nums):
    total = sum(nums)
    if total % 2 != 0: return False
    T = total // 2

    dp = [False] * (T + 1)
    dp[0] = True                       # zero is always makeable

    for num in nums:
        for t in range(T, num - 1, -1):     # downward! see the note
            dp[t] = dp[t] or dp[t - num]

    return dp[T]
Crucial Notethe descending inner loop is the entire difference between "each item once" and "each item unlimited times". It is not a micro-optimisation and it is not arbitrary — it is a correctness rule. Going downward means dp[t - num] has not yet been touched this round, so it still describes a world where num was not used. Going upward would let a single element be spent twice.
Worked Example:nums = [1, 5, 11, 5]
Total is 22, which is even, so T = 11. Start with only dp[0] = True.
- Process 1: reachable sums become {0, 1}.
- Process 5: from each existing sum, add 5. Reachable: {0, 1, 5, 6}.
- Process 11: adding 11 to 0 gives 11. Reachable: {0, 1, 5, 6, 11}. dp[11] is now true.
- Process 5: adds 5, 6, 10, 11, 16 (16 is past T and ignored). Reachable: {0, 1, 5, 6, 10, 11}.

dp[11] is true, so the answer is true — the subset {11} sums to 11 and the leftovers {1, 5, 5} also sum to 11.

Now the failing case [1, 2, 3, 5]: total 11 is odd, and we return false on the very first line without building anything.

And a case that survives the parity check but still fails, [1, 1, 3]: total 5 is odd — also rejected early. Try [2, 2, 3, 5]: total 12, T = 6, reachable sums are {0, 2, 3, 4, 5, 7, ...}; 6 never appears, so false. Even sums are not enough on their own.

What to carry away

Two ideas travel far beyond this problem.

The first is the reduction: a question about splitting into groups became a question about hitting one number, because fixing one group fixes the other. Whenever a problem asks you to divide things up, check whether one side determines the other — it very often does, and it halves the thinking.

The second is the subset-sum skeleton itself: walk the items, and at each one decide take-or-skip while carrying "how much budget is left" in the state. Swap the combining operation and the same skeleton answers a whole family of questions: or for "is it possible", + for "how many ways", max for "best value within budget". And the descending inner loop is what the "each item once" rule looks like in code: every item is taken zero times or one time, never more.

Exponential Try Every Subset
O(N × Sum) Time · O(N × Sum) Space Memoisation
O(N × Sum) Time · O(N × Sum) Space Tabulation
O(N × Sum) Time · O(Sum) Space One Row