Leetcode 416 - Partition Equal Subset Sum
Understanding the Problem
Given an integer array nums, return True if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or False otherwise.
Return:
Trueif a valid partition exists.Falseif it is impossible to split into two equal-sum subsets.
Key Data Structures
1. Hash Set (dp)
A dynamic set used to store all possible distinct subset sums attainable using elements processed so far.
2. Dynamic Programming Table / Array
An alternative implementation tool (1D boolean array) where dp[i] represents whether a subset sum of i is achievable.
Optimal Approach Intuition
This problem reduces to the 0/1 Knapsack Problem:
- Total Target: If
sum(nums)is odd, equal partition is mathematically impossible. If even, the target subset sum istarget = total_sum // 2. - Decision: For each number in
nums, decide whether to include it in the subset or exclude it. - Goal: Find if there exists any combination of numbers that sums exactly to
target.
Why 0/1 Knapsack (DP) over Plain DFS?
1. Exponential Complexity of Plain DFS
The naive DFS recursive approach explores all \(2^n\) subsets. Without memoization/DP, this causes Time Limit Exceeded (TLE) for larger inputs.
2. Overlapping Subproblems and Optimal Substructure
Different subsets can yield the same intermediate sum (e.g., [1, 4] and [2, 3] both sum to 5). Using dynamic programming lets us store unique achievable sums, avoiding duplicate computations and scaling efficiently relative to the target sum.
How the Optimal Approach Works
Step 1: Early Rejection
Check if sum(nums) is odd. If odd, return False immediately. Compute target = sum(nums) // 2.
Step 2: DP State Transitions (Set-Based)
Initialize a set dp = {0} (representing the initial base case sum of 0).
Iterate through each number n in nums:
1. Create a new temporary set next_dp.
2. For each existing sum t in dp:
- Keep t (exclude n).
- Add t + n (include n).
3. If target is in next_dp, return True early.
4. Update dp = next_dp.
Solution Implementation
1. Naive Brute Force (DFS - TLE)
from typing import List
class Solution:
def canPartition(self, nums: List[int]) -> bool:
total_sum = sum(nums)
if total_sum % 2 != 0:
return False
target = total_sum // 2
def dfs(idx, curr_sum):
if curr_sum == target:
return True
if idx >= len(nums) or curr_sum > target:
return False
return dfs(idx + 1, curr_sum + nums[idx]) or dfs(idx + 1, curr_sum)
return dfs(0, 0)
2. Optimal Approach (Dynamic Programming - Hash Set)
from typing import List
class Solution:
def canPartition(self, nums: List[int]) -> bool:
total_sum = sum(nums)
# Early exit if sum is odd
if total_sum % 2 != 0:
return False
target = total_sum // 2
dp = set([0])
for num in nums:
next_dp = set()
for t in dp:
if t + num == target:
return True
if t + num < target:
next_dp.add(t + num)
next_dp.add(t)
dp = next_dp
return target in dp
Complexity Analysis
Let:
- \(N\) = Length of
nums - \(S\) =
sum(nums) // 2(Target subset sum)
Time Complexity: \(O(N \times S)\)
- Sum Calculation: \(O(N)\) to calculate total sum.
- DP Iteration: Outer loop runs \(N\) times. The inner set holds at most \(S\) distinct sums.
Overall Time Complexity: \(O(N \times S)\)
Space Complexity: \(O(S)\)
- Hash Set (
dp): Holds at most \(S\) distinct target sums in the range \([0, target]\).
Overall Space Complexity: \(O(S)\)
Key Takeaways
- Reduction to Knapsack: Subsets with equal sums equal finding a single subset summing to \(\text{total\_sum} / 2\).
- Odd Sum Short-Circuit: If total sum is odd, integer division cannot divide it evenly, allowing an instant \(O(1)\) exit.
- Space-Efficient DP Set: Using a set automatically deduplicates redundant intermediate path sums.
Common Mistakes
Mistake 1: Not Pruning Sums Beyond Target
Adding values greater than target into the DP state needlessly inflates space and execution time.
Fix:
- Only store next sums where
t + num <= target.
Mistake 2: Modifying DP Set While Iterating
Modifying dp directly during iteration leads to runtime set modification errors or processing the same element multiple times within one step.
Fix:
- Build a new
next_dpset during each outer loop iteration, or iterate backwards when using a 1D DP array.
Mistake 3: Floating Point Operations
Using float division (total_sum / 2) can introduce floating-point inaccuracies.
Fix:
- Use integer division (
total_sum // 2).
Additional Resources
- https://neetcode.io/problems/partition-equal-subset-sum
- https://leetcode.com/problems/partition-equal-subset-sum/