You should aim for a solution as good or better than O(n * t) time and O(n * t) space, where n is the size of the input array and t is half the sum of the array elements.
Hint 1
If the sum of the array elements is not even, we can immediately return false. Think in terms of recursion, where we try to build a subset with a sum equal to half of the total sum. If we find such a subset, the remaining elements will automatically form another subset with the same sum. The entire array can also be considered as one subset, with the other being empty. Can you visualize this as a decision tree to process the array recursively?
Hint 2
We recursively iterate through the array with index i. At each step, we decide whether to include the current element in the subset or not. Instead of forming the subset explicitly, can you think of a better approach? Maybe you only need to track the subset sum rather than generating the subset itself.
Hint 3
We can track the subset sum using a variable curSum. At each step, we make two recursive calls. If adding the current element does not exceed the target, we include it. If either path leads to a solution, we immediately return true. Can you determine the base case for this recursion? All elements in the array are positive.
Hint 4
If curSum equals half the sum of the array elements, we return true. If index i goes out of bounds, we return false. This solution is exponential, but we can use memoization to cache recursive call results and avoid redundant computations. We can use a hash map or a 2D array with dimensions n * t, where n is the size of the input array and t is half the sum of the input array elements.
Company Tags
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Dynamic Programming (0/1 Knapsack Pattern) - This problem is a classic subset sum variant of the knapsack problem
Recursion with Memoization - Essential for the top-down DP approach to avoid redundant calculations
Subset Sum Problem - Core concept of determining if a subset with a target sum exists
Space Optimization in DP - Techniques to reduce 2D DP to 1D using reverse iteration
1. Recursion
Intuition
The problem asks whether we can split the array into two subsets with equal sum.
Key observation:
If the total sum is odd, it’s impossible → return False.
Otherwise, the problem becomes:
Can we pick a subset whose sum is totalSum / 2?
This is a classic subset sum decision problem.
Using recursion:
At each index, we have two choices:
Take the current number into the subset
Skip the current number
We keep reducing the target (sum/2) until:
Target becomes 0 → success
We run out of numbers or target becomes negative → failure
publicclassSolution{publicbooleancanPartition(int[] nums){int n = nums.length;int sum =0;for(int i =0; i < n; i++){
sum += nums[i];}if(sum %2!=0){returnfalse;}returndfs(nums,0, sum /2);}publicbooleandfs(int[] nums,int i,int target){if(i == nums.length){return target ==0;}if(target <0){returnfalse;}returndfs(nums, i +1, target)||dfs(nums, i +1, target - nums[i]);}}
classSolution{public:boolcanPartition(vector<int>& nums){int sum =0;for(int num : nums){
sum += num;}if(sum %2!=0){returnfalse;}returndfs(nums,0, sum /2);}booldfs(vector<int>& nums,int i,int target){if(i == nums.size()){return target ==0;}if(target <0){returnfalse;}returndfs(nums, i +1, target)||dfs(nums, i +1, target - nums[i]);}};
classSolution{/**
* @param {number[]} nums
* @return {boolean}
*/canPartition(nums){let sum = nums.reduce((a, b)=> a + b,0);if(sum %2!==0){returnfalse;}returnthis.dfs(nums,0, sum /2);}/**
* @params {number[]} nums
* @params {number} i
* @params {number} target
* @return {boolean}
*/dfs(nums, i, target){if(i === nums.length){return target ===0;}if(target <0){returnfalse;}return(this.dfs(nums, i +1, target)||this.dfs(nums, i +1, target - nums[i]));}}
publicclassSolution{publicboolCanPartition(int[] nums){int sum =0;for(int i =0; i < nums.Length; i++){
sum += nums[i];}if(sum %2!=0){returnfalse;}returnDfs(nums,0, sum /2);}publicboolDfs(int[] nums,int i,int target){if(i == nums.Length){return target ==0;}if(target <0){returnfalse;}returnDfs(nums, i +1, target)||Dfs(nums, i +1, target - nums[i]);}}
funccanPartition(nums []int)bool{
sum :=0for_, num :=range nums {
sum += num
}if sum%2!=0{returnfalse}
target := sum /2var dfs func(int,int)bool
dfs =func(i int, target int)bool{if target ==0{returntrue}if i >=len(nums)|| target <0{returnfalse}returndfs(i+1, target)||dfs(i+1, target-nums[i])}returndfs(0, target)}
classSolution:defcanPartition(self, nums: List[int])->bool:
total =sum(nums)if total %2!=0:returnFalse
target = total //2
n =len(nums)
memo =[[-1]*(target +1)for _ inrange(n +1)]defdfs(i, target):if target ==0:returnTrueif i >= n or target <0:returnFalseif memo[i][target]!=-1:return memo[i][target]
memo[i][target]=(dfs(i +1, target)or
dfs(i +1, target - nums[i]))return memo[i][target]return dfs(0, target)
publicclassSolution{Boolean[][] memo;publicbooleancanPartition(int[] nums){int n = nums.length;int sum =0;for(int i =0; i < n; i++){
sum += nums[i];}if(sum %2!=0){returnfalse;}
memo =newBoolean[n][sum /2+1];returndfs(nums,0, sum /2);}publicbooleandfs(int[] nums,int i,int target){if(i == nums.length){return target ==0;}if(target <0){returnfalse;}if(memo[i][target]!=null){return memo[i][target];}
memo[i][target]=dfs(nums, i +1, target)||dfs(nums, i +1, target - nums[i]);return memo[i][target];}}
classSolution{public:
vector<vector<int>> memo;boolcanPartition(vector<int>& nums){int sum =0;for(int num : nums){
sum += num;}if(sum %2!=0){returnfalse;}
memo.resize(nums.size(),vector<int>(sum /2+1,-1));returndfs(nums,0, sum /2);}booldfs(vector<int>& nums,int i,int target){if(i == nums.size()){return target ==0;}if(target <0){returnfalse;}if(memo[i][target]!=-1){return memo[i][target];}
memo[i][target]=dfs(nums, i +1, target)||dfs(nums, i +1, target - nums[i]);return memo[i][target];}};
classSolution{/**
* @param {number[]} nums
* @return {boolean}
*/canPartition(nums){let sum = nums.reduce((a, b)=> a + b,0);if(sum %2!==0){returnfalse;}const n = nums.length;this.memo = Array.from(Array(n +1),()=>Array(sum /2+1).fill(null),);returnthis.dfs(nums,0, sum /2);}/**
* @params {number[]} nums
* @params {number} i
* @params {number} target
* @return {boolean}
*/dfs(nums, i, target){if(i === nums.length){return target ===0;}if(target <0){returnfalse;}if(this.memo[i][target]!=null){returnthis.memo[i][target];}this.memo[i][target]=this.dfs(nums, i +1, target)||this.dfs(nums, i +1, target - nums[i]);returnthis.memo[i][target];}}
publicclassSolution{privatebool?[,] memo;publicboolCanPartition(int[] nums){int sum =0;for(int i =0; i < nums.Length; i++){
sum += nums[i];}if(sum %2!=0){returnfalse;}
memo =newbool?[nums.Length +1, sum /2+1];returnDfs(nums,0, sum /2);}publicboolDfs(int[] nums,int i,int target){if(target ==0){returntrue;}if(i == nums.Length || target <0){returnfalse;}if(memo[i, target]!=null){return memo[i, target]==true;}bool result =Dfs(nums, i +1, target)||Dfs(nums, i +1, target - nums[i]);
memo[i, target]= result;return result;}}
funccanPartition(nums []int)bool{
total :=0for_, num :=range nums {
total += num
}if total%2!=0{returnfalse}
target := total /2
n :=len(nums)
memo :=make([][]int, n+1)for i :=range memo {
memo[i]=make([]int, target+1)for j :=range memo[i]{
memo[i][j]=-1}}var dfs func(int,int)bool
dfs =func(i, target int)bool{if target ==0{returntrue}if i >= n || target <0{returnfalse}if memo[i][target]!=-1{return memo[i][target]==1}
found :=dfs(i+1, target)||dfs(i+1, target-nums[i])if found {
memo[i][target]=1}else{
memo[i][target]=0}return found
}returndfs(0, target)}
class Solution {funcanPartition(nums: IntArray): Boolean {val total = nums.sum()if(total %2!=0)returnfalseval target = total /2val n = nums.size
val memo =Array(n +1){IntArray(target +1){-1}}fundfs(i: Int, target: Int): Boolean {if(target ==0)returntrueif(i >= n || target <0)returnfalseif(memo[i][target]!=-1)return memo[i][target]==1val found =dfs(i +1, target)||dfs(i +1, target - nums[i])
memo[i][target]=if(found)1else0return found
}returndfs(0, target)}}
classSolution{funccanPartition(_ nums:[Int])->Bool{let total = nums.reduce(0,+)if total %2!=0{returnfalse}let target = total /2let n = nums.count
var memo =Array(repeating:Array(repeating:-1, count: target +1), count: n +1)funcdfs(_ i:Int,_ target:Int)->Bool{if target ==0{returntrue}if i >= n || target <0{returnfalse}if memo[i][target]!=-1{return memo[i][target]==1}let result =dfs(i +1, target)||dfs(i +1, target - nums[i])
memo[i][target]= result ?1:0return result
}returndfs(0, target)}}
Time & Space Complexity
Time complexity: O(n∗target)
Space complexity: O(n∗target)
Where n is the length of the array nums and target is the sum of array elements divided by 2.
3. Dynamic Programming (Bottom-Up)
Intuition
This is a classic 0/1 subset sum DP.
We want to split nums into two subsets with equal sum. That’s only possible if:
total = sum(nums) is even
there exists a subset that sums to target = total / 2
Instead of trying all subsets (exponential), we build the answer gradually:
Let dp[i][j] mean: using the first i numbers, can we form sum j?
For each number, we have two choices:
Skip it → the possibility stays dp[i-1][j]
Take it (only if nums[i-1] <= j) → check dp[i-1][j - nums[i-1]]
If either is true, then dp[i][j] is true.
Algorithm
Compute total = sum(nums). If total is odd, return false.
Set target = total // 2, n = len(nums).
Create a DP table dp of size (n+1) x (target+1) filled with false.
Base case: dp[i][0] = true for all i (sum 0 is always achievable by taking nothing).
classSolution:defcanPartition(self, nums: List[int])->bool:ifsum(nums)%2:returnFalse
dp =set()
dp.add(0)
target =sum(nums)//2for i inrange(len(nums)-1,-1,-1):
nextDP =set()for t in dp:if(t + nums[i])== target:returnTrue
nextDP.add(t + nums[i])
nextDP.add(t)
dp = nextDP
returnFalse
publicclassSolution{publicbooleancanPartition(int[] nums){if(Arrays.stream(nums).sum()%2!=0){returnfalse;}Set<Integer> dp =newHashSet<>();
dp.add(0);int target =Arrays.stream(nums).sum()/2;for(int i = nums.length -1; i >=0; i--){Set<Integer> nextDP =newHashSet<>();for(int t : dp){if(t + nums[i]== target){returntrue;}
nextDP.add(t + nums[i]);
nextDP.add(t);}
dp = nextDP;}returnfalse;}}
classSolution{public:boolcanPartition(vector<int>& nums){int sum =0;for(int num : nums){
sum += num;}if(sum %2!=0){returnfalse;}
unordered_set<int> dp;
dp.insert(0);int target = sum /2;for(int i = nums.size()-1; i >=0; i--){
unordered_set<int> nextDP;for(int t : dp){if(t + nums[i]== target){returntrue;}
nextDP.insert(t + nums[i]);
nextDP.insert(t);}
dp = nextDP;}returnfalse;}};
classSolution{/**
* @param {number[]} nums
* @return {boolean}
*/canPartition(nums){const sum = nums.reduce((acc, num)=> acc + num,0);if(sum %2!==0){returnfalse;}let dp =newSet();
dp.add(0);const target = sum /2;for(let i = nums.length -1; i >=0; i--){const nextDP =newSet();for(const t of dp){if(t + nums[i]=== target){returntrue;}
nextDP.add(t + nums[i]);
nextDP.add(t);}
dp = nextDP;}returnfalse;}}
publicclassSolution{publicboolCanPartition(int[] nums){if(nums.Sum()%2!=0){returnfalse;}HashSet<int> dp =newHashSet<int>();
dp.Add(0);int target = nums.Sum()/2;for(int i = nums.Length -1; i >=0; i--){HashSet<int> nextDP =newHashSet<int>();foreach(int t in dp){if(t + nums[i]== target){returntrue;}
nextDP.Add(t + nums[i]);
nextDP.Add(t);}
dp = nextDP;}returnfalse;}}
funccanPartition(nums []int)bool{
total :=0for_, num :=range nums {
total += num
}if total%2!=0{returnfalse}
target := total /2
dp :=map[int]bool{0:true}for i :=len(nums)-1; i >=0; i--{
nextDP :=map[int]bool{}for t :=range dp {if t+nums[i]== target {returntrue}
nextDP[t+nums[i]]=true
nextDP[t]=true}
dp = nextDP
}returnfalse}
class Solution {funcanPartition(nums: IntArray): Boolean {val total = nums.sum()if(total %2!=0)returnfalseval target = total /2var dp =hashSetOf(0)for(i in nums.size -1 downTo 0){val nextDP = HashSet<Int>()for(t in dp){if(t + nums[i]== target)returntrue
nextDP.add(t + nums[i])
nextDP.add(t)}
dp = nextDP
}returnfalse}}
classSolution{funccanPartition(_ nums:[Int])->Bool{if nums.reduce(0,+)%2!=0{returnfalse}var dp:Set<Int>=[0]let target = nums.reduce(0,+)/2for i instride(from: nums.count -1, through:0, by:-1){var nextDP:Set<Int>=[]for t in dp {if t + nums[i]== target {returntrue}
nextDP.insert(t + nums[i])
nextDP.insert(t)}
dp = nextDP
}returnfalse}}
Time & Space Complexity
Time complexity: O(n∗target)
Space complexity: O(target)
Where n is the length of the array nums and target is the sum of array elements divided by 2.
6. Dynamic Programming (Optimal)
Intuition
This is the most optimal DP solution for Partition Equal Subset Sum.
We reduce the problem to:
Can we pick some numbers whose sum equals target = total_sum / 2?
We use a 1D DP array where:
dp[j] = True means we can form sum j using some of the numbers so far.
Key idea:
For each number, update the DP from right to left so that each number is used only once.
This avoids overwriting results from the same iteration.
Algorithm
Compute total = sum(nums). If total is odd, return false.
Set target = total // 2.
Create a boolean array dp of size target + 1.
Initialize dp[0] = true (sum 0 is always achievable).
classSolution:defcanPartition(self, nums:list[int])->bool:
total =sum(nums)if total %2!=0:returnFalse
target = total //2
dp =1<<0for num in nums:
dp |= dp << num
return(dp &(1<< target))!=0
classSolution{public:boolcanPartition(vector<int>& nums){int sum =0;for(int num : nums){
sum += num;}if(sum %2!=0){returnfalse;}int target = sum /2;
bitset<10001> dp;
dp[0]=1;for(int num : nums){
dp |= dp << num;}return dp[target];}};
Time & Space Complexity
Time complexity: O(n∗target)
Space complexity: O(target)
Where n is the length of the array nums and target is the sum of array elements divided by 2.
Common Pitfalls
Forgetting the Odd Sum Check
The most common mistake is forgetting to check if the total sum is odd before proceeding. If sum(nums) is odd, it's impossible to split into two equal subsets, and you should immediately return false. Skipping this check leads to incorrect results or wasted computation.
Using Left-to-Right Iteration in Space-Optimized DP
When using a 1D DP array, iterating from left to right causes elements to be counted multiple times in the same iteration. You must iterate from right to left (target down to num) to ensure each element is used at most once per subset, preserving the 0/1 knapsack property.
Integer Overflow in Target Calculation
For languages without arbitrary precision integers, the sum of array elements can overflow if not handled carefully. Always use appropriate data types (like long in Java/C++) when computing the total sum before dividing by 2 to get the target.
Sign in to join the discussion