150NeetCode 150

110.Partition Equal Subset Sum

Medium1-D Dynamic Programming ยท 12/12

Problem

Given an array of positive integers nums, decide whether it can be split into two subsets with equal sums. Every element must go into exactly one of the two subsets.

Return true if such a partition exists, otherwise false.

Examples

Input:  nums = [1, 5, 11, 5]
Output: true
Explanation: [1, 5, 5] and [11] both sum to 11.
Input:  nums = [1, 2, 3, 5]
Output: false
Explanation: The total is 11, which is odd โ€” it can't be split evenly.
Input:  nums = [2, 2, 3, 5]
Output: false
Explanation: Total 12, target 6, but no subset sums to 6.

Constraints

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

๐Ÿ’ก Key Insight

If the total S is odd, the answer is false. Otherwise the question becomes "is there a subset summing to target = S / 2?" โ€” the other elements automatically sum to S / 2 too. That's 0/1 knapsack: can(i, t) = can(i + 1, t) || can(i + 1, t - nums[i]) (skip or take element i). Since target <= 200 ยท 100 / 2 = 10,000, the table is small, and iterating t downwards lets one boolean array of size target + 1 do the work of the whole 2-D table.

Recursion (Brute Force)

TimeO(2โฟ)SpaceO(n)

Intuition

For each element decide: put it in the subset that should reach target, or leave it out. Succeed if the remaining target hits exactly 0.

Recurrence: can(i, t) = true if t == 0; false if i == n or t < 0; otherwise can(i + 1, t) || can(i + 1, t - nums[i]).

Algorithm

  1. sum all elements; if odd, return false.
  2. dfs(i, t) as in the recurrence.
  3. Return dfs(0, sum / 2).

Visualization

nums = [1, 5, 11, 5], target = 11 (partial). The state (2, 5) โ€” "from index 2, still need 5" โ€” is reached twice.

flowchart TD
    A["can(0, 11)"] -->|"take 1"| B["can(1, 10)"]
    A -->|"skip 1"| C["can(1, 11)"]
    B -->|"take 5"| D["can(2, 5)"]
    B -->|"skip 5"| E["can(2, 10)"]
    C -->|"take 5"| F["can(2, 6)"]
    C -->|"skip 5"| G["can(2, 11)"]
    D -->|"skip 11"| H["can(3, 5)"]
    H -->|"take 5"| I["can(4, 0) = true"]
    G -->|"take 11"| J["can(3, 0) = true"]

Code

java
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int x : nums) {
            sum += x;
        }
        if (sum % 2 != 0) {
            return false; // odd total can't be halved
        }
        return dfs(nums, 0, sum / 2);
    }

    // can some subset of nums[i..] sum to exactly t?
    private boolean dfs(int[] nums, int i, int t) {
        if (t == 0) {
            return true;
        }
        if (i == nums.length || t < 0) {
            return false;
        }
        return dfs(nums, i + 1, t) || dfs(nums, i + 1, t - nums[i]);
    }
}

Complexity

  • Time: O(2โฟ) โ€” each element is in or out.
  • Space: O(n) โ€” recursion depth.

Complexity Comparison

Approach Time Space
Recursion (Brute Force) O(2โฟ) O(n)
Top-Down Memoization O(n ยท target) O(n ยท target)
Bottom-Up Tabulation O(n ยท target) O(n ยท target)
1-D Space-Optimized DP O(n ยท target) O(target)

Common Pitfalls

  • Skipping the odd-sum check โ€” then sum / 2 truncates and you search for the wrong target.
  • Iterating t upwards in the 1-D version: dp[t - num] would already include num, turning it into unbounded knapsack (e.g. [1, 2, 5] would wrongly "reach" 4 = 2 + 2).
  • Allocating the memo as boolean[][] โ€” false is a valid answer, so there's no "not computed" state; use Boolean[][].
  • Calling it "split into two subarrays" โ€” the subsets need not be contiguous.