Partition Equal Subset Sum – Solution & Complexity

Solution Walkthrough

  1. Step 1: Reduce to subset sum

    • Splitting into two equal halves means finding a subset that sums to exactly total / 2.
    • If total is odd, no such subset exists, so return false immediately.
    • Otherwise the question becomes: can any subset reach target = total / 2?
  2. Step 2: Fill a 2D reachability table

    • Let dp[i][s] be true when some subset of the first i values sums to s.
    • A sum of 0 is always reachable with the empty subset.
    • Each value is either skipped (dp[i-1][s]) or taken (dp[i-1][s - nums[i-1]]).
    def can_partition(nums: list[int]) -> bool:
        total = sum(nums)
        if total % 2 != 0:
            return False
        target = total // 2
        n = len(nums)
        dp = [[False] * (target + 1) for _ in range(n + 1)]
        for i in range(n + 1):
            dp[i][0] = True
        for i in range(1, n + 1):
            for s in range(1, target + 1):
                dp[i][s] = dp[i - 1][s]
                if s >= nums[i - 1] and dp[i - 1][s - nums[i - 1]]:
                    dp[i][s] = True
        return dp[n][target]
  3. Step 3: Compress to one boolean row

    • Each row only reads the row directly above, so a single boolean array of size target + 1 suffices.
    • Iterate the inner sum from high down to value; going downward guarantees each number is used at most once (a 0/1 knapsack, not unbounded).
  4. Step 4: Optimal 1D solution

    • Start with dp[0] = true.
    • For every value, mark dp[sum] reachable whenever dp[sum - value] was already reachable, scanning sums downward.
    • The answer is dp[target].
    def can_partition(nums: list[int]) -> bool:
        total = sum(nums)
        if total % 2 != 0:
            return False
        target = total // 2
        dp = [False] * (target + 1)
        dp[0] = True
        for value in nums:
            for s in range(target, value - 1, -1):
                if dp[s - value]:
                    dp[s] = True
        return dp[target]
  5. Step 5: Dry run

    Trace nums = [1,5,11,5], target = 11.

    • Start: reachable sums {0}.
    • After 1: {0,1}.
    • After 5: {0,1,5,6}.
    • After 11: {0,1,5,6,11,...} — 11 is now reachable, so the answer is true.
  6. Step 6: Common mistakes and follow-ups

    • Iterating the inner sum upward, which reuses the same element multiple times and turns it into unbounded knapsack.
    • Forgetting the odd-total shortcut, or comparing against total instead of total / 2.
    • Allocating dp of size target instead of target + 1 and missing the exact-target cell.
    • Follow-up: how would you recover the actual subset, not just whether one exists?
  7. Step 7: Edge cases to test mentally

    • A single element can never be split, so the answer is false.
    • An odd total is always false.
    • Two equal elements are always true.
  8. Step 8: Final full solution and complexity

    Target half the total and fill a 1D subset-sum table, scanning sums downward per value. Time is O(n * sum) and extra space is O(sum).

    def can_partition(nums: list[int]) -> bool:
        total = sum(nums)
        if total % 2 != 0:
            return False
        target = total // 2
        dp = [False] * (target + 1)
        dp[0] = True
        for value in nums:
            for s in range(target, value - 1, -1):
                if dp[s - value]:
                    dp[s] = True
        return dp[target]

FAQ