Partition Equal Subset Sum
Problem
We are given a non-empty array of positive integers nums. Our task is to check whether this array can be divided into two subsets such that the sum of both subsets are equal.
For example,
Input: nums = [1,5,11,5] Output: true Explanation: The array can be partitioned as [1, 5, 5] and [11].
Input: nums = [1,2,3,5] Output: false Explanation: The array cannot be partitioned into equal sum subsets.
Constraints:
1 <= nums.length <= 2001 <= nums[i] <= 100
Solution
Brute Force
public bool CanPartition(int[] nums)
{
int sum = nums.Sum();
if (sum % 2 == 1)
return false;
int subSetSum = sum / 2;
return CanPartition(nums.Length - 1, subSetSum);
bool CanPartition(int n, int subSetSum)
{
if (subSetSum == 0)
return true;
if (n == 0 || subSetSum < 0)
return false;
return CanPartition(n - 1, subSetSum - nums[n - 1]) || CanPartition(n - 1, subSetSum);
}
}
Complexity Analysis
- Time Complexity: At each step we generate two subtree and the height of the tree is
n. The total number of generated nodes is 2n. Since, we traverse all the nodes, then the complexity will be O(2n). - Space Complexity: The complexity will be the same with O(recursion depth), thus it will be O(n).
Top-down Dynamic Programming
public bool CanPartition(int[] nums)
{
int sum = nums.Sum();
if (sum % 2 == 1)
return false;
int subSetSum = sum / 2;
Dictionary<(int, int), bool> memo = new Dictionary<(int, int), bool>();
return CanPartition(nums.Length - 1, subSetSum);
bool CanPartition(int n, int subSetSum)
{
if (subSetSum == 0)
return true;
if (n == 0 || subSetSum < 0)
return false;
if (memo.TryGetValue((n, subSetSum), out var value))
return value;
bool result = CanPartition(n - 1, subSetSum - nums[n - 1]) || CanPartition(n - 1, subSetSum);
memo.Add((n, subSetSum), result);
return result;
}
}
Complexity Analysis
Suppose N is the length of nums and M equals the subSetSum.
- Time Complexity: This problem doesn’t guarantee to have overlapping subproblem unlike typical dynamic programming problem. Therefore, in the worst case the time complexity is O(N x M).
- Space Complexity: The memo will contain N x M entries and the recursion stack will allocate spaces as many as the depth of recursive call (tree), thus the complexity will be O(N x M) since N x M is the dominant part.
Bottom-up Dynamic Programming
public bool CanPartition(int[] nums)
{
int sum = nums.Sum();
if (sum % 2 == 1)
return false;
int subSetSum = sum / 2;
int n = nums.Length;
bool[,] dp = new bool[n + 1, subSetSum + 1];
dp[0, 0] = true;
for (int i = 1; i <= n; i++)
{
int curr = nums[i - 1];
for (int j = 0; j <= subSetSum; j++)
{
if (j < curr)
dp[i, j] = dp[i - 1, j];
else
dp[i, j] = dp[i - 1, j] || (dp[i - 1, j - curr]);
}
}
return dp[n, subSetSum];
}
Complexity Analysis
Suppose N is the length of nums and M equals the subSetSum.
- Time Complexity: Since we have nested loop where the outer loop has length
Nand the inner loop has lengthM, then the time complexity is O(N x M). - Space Complexity: Since the
dparray will allocate around N x M spaces, then the complexity will be O(N x M).