Facebook Pixel

416. Partition Equal Subset Sum

Problem Description

You are given an integer array nums. Your task is to determine if you can split this array into two subsets where both subsets have equal sums.

For example, if you have the array [1, 5, 11, 5], you can partition it into [1, 5, 5] and [11], where both subsets sum to 11. Therefore, the answer would be true.

However, if you have the array [1, 2, 3, 5], the total sum is 11 (which is odd), so it's impossible to split it into two equal-sum subsets. The answer would be false.

The problem asks you to return true if such a partition is possible, and false otherwise.

Key observations:

  • If the total sum of the array is odd, it's impossible to partition it into two equal subsets
  • If the total sum is even, the problem becomes: can we find a subset that sums to exactly half of the total sum?
  • This is essentially a subset sum problem where the target is sum(nums) / 2
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that if we can partition an array into two subsets with equal sums, then each subset must have a sum equal to exactly half of the total array sum. This immediately tells us that if the total sum is odd, partitioning is impossible.

When the total sum is even, we need to find if there's a subset that sums to target = total_sum / 2. If such a subset exists, the remaining elements will automatically form another subset with the same sum.

This transforms our problem into the classic "Subset Sum" problem: can we select some elements from the array that sum to a specific target?

To solve this, we can think about it step by step. For each element in the array, we have two choices: either include it in our subset or exclude it. This leads us to think about dynamic programming.

We can build up our solution by considering: "Can we achieve sum j using the first i elements?" For each element x at position i:

  • If we don't include x, then achieving sum j depends on whether we could achieve sum j using the first i-1 elements
  • If we include x, then achieving sum j depends on whether we could achieve sum j-x using the first i-1 elements (we need j-x from previous elements plus x to make j)

Starting from the base case where we can achieve sum 0 with 0 elements, we can build up a table that tells us whether each sum is achievable with each subset of elements, ultimately answering whether sum target is achievable using all available elements.

Solution Approach

We implement the solution using dynamic programming with a 2D boolean array.

First, we calculate the total sum of nums and check if it's even:

m, mod = divmod(sum(nums), 2)
if mod:
    return False

If the sum is odd (mod != 0), we return false immediately. Otherwise, our target sum is m = sum(nums) // 2.

Next, we create a 2D DP table f where f[i][j] represents whether we can achieve sum j using the first i numbers:

f = [[False] * (m + 1) for _ in range(n + 1)]

The table has dimensions (n+1) × (m+1) to handle the base case and all possible sums from 0 to m.

We initialize the base case:

f[0][0] = True

This means we can achieve sum 0 using 0 elements (by selecting nothing).

Now we fill the DP table iteratively. For each number x at position i (1-indexed) and each possible sum j from 0 to m:

for i, x in enumerate(nums, 1):
    for j in range(m + 1):
        f[i][j] = f[i - 1][j] or (j >= x and f[i - 1][j - x])

The state transition equation is:

  • f[i][j] = f[i - 1][j] - we don't select the current number x, so we inherit the result from using the first i-1 numbers
  • If j >= x, we can also try selecting x: f[i][j] = f[i][j] or f[i - 1][j - x] - we check if we could achieve sum j - x with the first i-1 numbers

The condition j >= x ensures we only try to include x when the target sum j is at least as large as x.

Finally, we return f[n][m], which tells us whether we can achieve sum m using all n numbers (though we don't have to use all of them - we just have them available for selection).

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the example nums = [1, 5, 11, 5] to illustrate the solution approach.

Step 1: Check if partition is possible

  • Total sum = 1 + 5 + 11 + 5 = 22 (even)
  • Target sum for each subset = 22 / 2 = 11
  • Since the sum is even, we proceed with DP

Step 2: Initialize DP table We create a table f[i][j] where i represents "using first i numbers" and j represents "achieving sum j".

  • Dimensions: 5 rows (0 to 4 numbers) × 12 columns (sums 0 to 11)
  • Base case: f[0][0] = True (we can achieve sum 0 with 0 numbers)

Step 3: Fill the DP table iteratively

Processing nums[0] = 1 (i = 1):

  • For each sum j from 0 to 11:
    • f[1][0] = f[0][0] = True (don't take 1)
    • f[1][1] = f[0][0] = True (take 1: 0 + 1 = 1)
    • f[1][2...11] = False (can't achieve these sums with just 1)

Processing nums[1] = 5 (i = 2):

  • Notable updates:
    • f[2][0] = True (don't take anything)
    • f[2][1] = True (inherited from f[1][1])
    • f[2][5] = f[1][0] = True (take 5: 0 + 5 = 5)
    • f[2][6] = f[1][1] = True (take 5: 1 + 5 = 6)

Processing nums[2] = 11 (i = 3):

  • Notable updates:
    • Previous sums remain achievable
    • f[3][11] = f[2][0] = True (take 11: 0 + 11 = 11) ✓

At this point, f[3][11] = True, meaning we can achieve sum 11 using the first 3 numbers. We could stop here, but let's complete the table.

Processing nums[3] = 5 (i = 4):

  • f[4][11] remains True (inherited from f[3][11])

Step 4: Return result f[4][11] = True, confirming we can partition the array into two equal-sum subsets.

Indeed, we can form subset [1, 5, 5] with sum 11, leaving [11] with sum 11.

Solution Implementation

1class Solution:
2    def canPartition(self, nums: List[int]) -> bool:
3        # Calculate total sum and check if it can be divided into two equal parts
4        total_sum = sum(nums)
5        target, remainder = divmod(total_sum, 2)
6      
7        # If sum is odd, we cannot partition into two equal subsets
8        if remainder != 0:
9            return False
10      
11        n = len(nums)
12      
13        # DP table: dp[i][j] represents whether we can achieve sum j using first i numbers
14        # dp[i][j] = True if we can make sum j using nums[0...i-1]
15        dp = [[False] * (target + 1) for _ in range(n + 1)]
16      
17        # Base case: we can always make sum 0 with empty subset
18        dp[0][0] = True
19      
20        # Fill the DP table
21        for i in range(1, n + 1):
22            current_num = nums[i - 1]  # Current number (adjust for 0-indexed nums array)
23          
24            for j in range(target + 1):
25                # Two choices for each number:
26                # 1. Don't include current number: dp[i-1][j]
27                # 2. Include current number (if possible): dp[i-1][j-current_num]
28                dp[i][j] = dp[i - 1][j]  # Don't include current number
29              
30                if j >= current_num:  # Can include current number
31                    dp[i][j] = dp[i][j] or dp[i - 1][j - current_num]
32      
33        # Return whether we can achieve the target sum using all n numbers
34        return dp[n][target]
35
1class Solution {
2    public boolean canPartition(int[] nums) {
3        // Calculate the total sum of all elements
4        int totalSum = 0;
5        for (int num : nums) {
6            totalSum += num;
7        }
8      
9        // If total sum is odd, we cannot partition into two equal subsets
10        if (totalSum % 2 == 1) {
11            return false;
12        }
13      
14        int n = nums.length;
15        int targetSum = totalSum / 2;  // Each subset should sum to half of total
16      
17        // dp[i][j] represents whether we can achieve sum j using first i elements
18        boolean[][] dp = new boolean[n + 1][targetSum + 1];
19      
20        // Base case: we can always achieve sum 0 with 0 elements
21        dp[0][0] = true;
22      
23        // Fill the DP table
24        for (int i = 1; i <= n; i++) {
25            int currentNum = nums[i - 1];
26          
27            for (int j = 0; j <= targetSum; j++) {
28                // Option 1: Don't include current number
29                dp[i][j] = dp[i - 1][j];
30              
31                // Option 2: Include current number (if it doesn't exceed target)
32                if (j >= currentNum) {
33                    dp[i][j] = dp[i][j] || dp[i - 1][j - currentNum];
34                }
35            }
36        }
37      
38        // Return whether we can achieve targetSum using all n elements
39        return dp[n][targetSum];
40    }
41}
42
1class Solution {
2public:
3    bool canPartition(vector<int>& nums) {
4        // Calculate the total sum of all elements
5        int totalSum = accumulate(nums.begin(), nums.end(), 0);
6      
7        // If total sum is odd, we cannot partition into two equal subsets
8        if (totalSum % 2 == 1) {
9            return false;
10        }
11      
12        int n = nums.size();
13        int targetSum = totalSum / 2;  // Target sum for each subset
14      
15        // dp[i][j] represents whether we can achieve sum j using first i elements
16        bool dp[n + 1][targetSum + 1];
17        memset(dp, false, sizeof(dp));
18      
19        // Base case: we can always achieve sum 0 with 0 elements
20        dp[0][0] = true;
21      
22        // Fill the DP table
23        for (int i = 1; i <= n; ++i) {
24            int currentNum = nums[i - 1];
25          
26            for (int j = 0; j <= targetSum; ++j) {
27                // Two choices for each element:
28                // 1. Don't include current element: dp[i-1][j]
29                // 2. Include current element (if possible): dp[i-1][j-currentNum]
30                dp[i][j] = dp[i - 1][j] || (j >= currentNum && dp[i - 1][j - currentNum]);
31            }
32        }
33      
34        // Return whether we can achieve targetSum using all n elements
35        return dp[n][targetSum];
36    }
37};
38
1/**
2 * Determines if the array can be partitioned into two subsets with equal sum
3 * @param nums - Array of positive integers
4 * @returns true if the array can be partitioned into two equal sum subsets, false otherwise
5 */
6function canPartition(nums: number[]): boolean {
7    // Calculate the total sum of all elements
8    const totalSum: number = nums.reduce((accumulator, current) => accumulator + current, 0);
9  
10    // If the total sum is odd, we cannot partition into two equal subsets
11    if (totalSum % 2 === 1) {
12        return false;
13    }
14  
15    // Get the array length
16    const arrayLength: number = nums.length;
17  
18    // Calculate the target sum for each subset (half of total sum)
19    const targetSum: number = totalSum >> 1; // Using bit shift for division by 2
20  
21    // Create a 2D DP table where dp[i][j] represents whether we can achieve sum j
22    // using the first i elements from the array
23    const dp: boolean[][] = Array.from(
24        { length: arrayLength + 1 }, 
25        () => Array(targetSum + 1).fill(false)
26    );
27  
28    // Base case: we can always achieve sum 0 with 0 elements
29    dp[0][0] = true;
30  
31    // Fill the DP table
32    for (let i = 1; i <= arrayLength; ++i) {
33        // Get the current element (adjusting for 0-based indexing)
34        const currentNum: number = nums[i - 1];
35      
36        // Check all possible sums from 0 to targetSum
37        for (let j = 0; j <= targetSum; ++j) {
38            // We have two choices:
39            // 1. Don't include the current number: dp[i-1][j]
40            // 2. Include the current number (if possible): dp[i-1][j-currentNum]
41            dp[i][j] = dp[i - 1][j] || (j >= currentNum && dp[i - 1][j - currentNum]);
42        }
43    }
44  
45    // Return whether we can achieve the target sum using all elements
46    return dp[arrayLength][targetSum];
47}
48

Time and Space Complexity

The time complexity is O(m × n), where m is half of the total sum of the array and n is the length of the array. The algorithm uses a 2D dynamic programming approach with a nested loop structure - the outer loop iterates through all n elements in the array, and the inner loop iterates through all possible sums from 0 to m. Each cell computation f[i][j] = f[i - 1][j] or (j >= x and f[i - 1][j - x]) takes O(1) time, resulting in an overall time complexity of O(m × n).

The space complexity is O(m × n). The algorithm creates a 2D boolean array f with dimensions (n + 1) × (m + 1) to store the dynamic programming states. Each cell in this array represents whether it's possible to achieve a sum of j using the first i elements from the input array. Since we're storing all intermediate results in this 2D table, the space requirement is proportional to O((n + 1) × (m + 1)), which simplifies to O(m × n).

Common Pitfalls

1. Space Complexity Optimization Overlooked

The current solution uses O(n × target) space with a 2D DP table. Since each row only depends on the previous row, we can optimize this to O(target) space using a 1D array.

Issue: Using unnecessary memory when dealing with large arrays or large sum values.

Solution: Use a 1D DP array and iterate backwards to avoid overwriting values we still need:

def canPartition(self, nums: List[int]) -> bool:
    total_sum = sum(nums)
    target, remainder = divmod(total_sum, 2)
  
    if remainder != 0:
        return False
  
    # Use 1D DP array instead of 2D
    dp = [False] * (target + 1)
    dp[0] = True
  
    for num in nums:
        # Iterate backwards to avoid using updated values
        for j in range(target, num - 1, -1):
            dp[j] = dp[j] or dp[j - num]
  
    return dp[target]

2. Integer Overflow in Other Languages

While Python handles large integers automatically, in languages like Java or C++, calculating sum(nums) could cause integer overflow for large arrays with large values.

Issue: Getting incorrect results due to overflow when calculating the total sum.

Solution: Use appropriate data types (like long in Java) or check for overflow conditions:

// Java example
long totalSum = 0;
for (int num : nums) {
    totalSum += num;
}

3. Early Termination Opportunities Missed

The current solution always processes all elements even when we've already found that we can achieve the target sum.

Issue: Wasting computation time when the answer could be determined earlier.

Solution: Add early termination checks:

def canPartition(self, nums: List[int]) -> bool:
    total_sum = sum(nums)
    target, remainder = divmod(total_sum, 2)
  
    if remainder != 0:
        return False
  
    # Early termination: if any single element equals target
    if target in nums:
        return True
  
    # Early termination: if largest element > target
    if max(nums) > target:
        return False
  
    dp = [False] * (target + 1)
    dp[0] = True
  
    for num in nums:
        for j in range(target, num - 1, -1):
            dp[j] = dp[j] or dp[j - num]
      
        # Early termination: check if target is achieved
        if dp[target]:
            return True
  
    return dp[target]

4. Not Handling Edge Cases

Failing to handle special cases like empty arrays or arrays with single elements.

Issue: Runtime errors or incorrect results for edge cases.

Solution: Add edge case checks at the beginning:

def canPartition(self, nums: List[int]) -> bool:
    # Edge cases
    if not nums or len(nums) < 2:
        return False
  
    total_sum = sum(nums)
    target, remainder = divmod(total_sum, 2)
  
    if remainder != 0:
        return False
  
    # Continue with DP solution...

5. Incorrect Index Mapping in 2D DP

When using 2D DP, confusion between 0-indexed arrays and 1-indexed DP table can lead to off-by-one errors.

Issue: Accessing nums[i] instead of nums[i-1] when i represents the DP table row (1-indexed).

Solution: Be consistent with indexing and add clear comments:

# Correct indexing
for i in range(1, n + 1):
    current_num = nums[i - 1]  # i is 1-indexed for DP, nums is 0-indexed
    for j in range(target + 1):
        dp[i][j] = dp[i - 1][j]
        if j >= current_num:
            dp[i][j] = dp[i][j] or dp[i - 1][j - current_num]
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More