CLOSE
🛠️ Settings

Problem Statement

Given an array arr of n integers, return true if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal else return false.

LeetCode:

Constraints:

1 <= nums.length <= 200
1 <= nums[i] <= 100
  • 1 ≤ nums.length ≤ 200
    • The array nums must contain at least 1 and at most 200 elements.
    • This tells us the input size we are working with. A maximum of 200 is relatively small, so algorithms with time complexity up to O(n^2) (nested loops) are generally acceptable.
  • 1 ≤ nums[i] ≤ 100
    • Each element of the nums array is a positive integer between 1 and 100, inclusive.
    • No need to handle negative or zero values.
    • The range is small, which may allow optimization techniques like counting arrays, prefix sums, or frequency maps.

Examples

Example 1:

Input: arr = [1, 10, 21, 10]
Output: True

Explanation: The array can be partitioned as [1, 10, 10] and [21]
Example 2:

Input: arr = [1, 2, 3, 5]
Output: False

Explanation: The array cannot be partitioned into equal sum subsets.

Different Approaches

1️⃣ Recursive Approach

This question is a slight modification of the problem discussed in Subset-sum equal to target. We need to partition the array(say S) into two subsets(say S1 and S2). According to the question:

  • Sum of elements of S1 + sum of elements of S2 = sum of elements of S.
  • Sum of elements of S1 = sum of elements of S2.
  • These two conditions imply that S1 = S2 = (S/2).
image-305.png

Observation:

  • If S (sum of elements of the input array) is odd , there is no way we can divide it into two equal halves, so we can simply return false.
  • If S is even, then we need to find a subset in the input array whose sum is equal to S/2 because if we find one subset with sum S/2, the remaining elements sum will be automatically S/2. So, we can partition the given array. Hence we return true.

From here try to find a subset in the array with target = S/2 as discussed in Subset-sum equal to target.

The idea is to generate all pssible subsets and whenever a single subsets is found whose sum is equal to the given target(S/2), simply return true. As, all the subsets needs to generated, recursion can be used to solve this problem.

Steps to form the recursive solution:

  • Express the problem in terms of indexes: The array will have an index but there is one more parameter “target”. So, we can say that initially, we need to find(n-1, target) which means that we need to find whether there exists a subset in the array from index 0 to n-1, whose sum is equal to the target.
    • So, f(ind, target) will check whether a subset exists in the array from index 0 to index 'ind' such that the sum of elements is equal to the target.
  • Try out all possible choices at a given index: As all the subsets needs to be generated, we will use the pick/non-pick technique. There will be two choices in each function call:
    • Do not include the current element in the subset: First try to find a subset without considering the current index element. For this, make a recursive call to f(ind-1,target).
    • Include the current element in the subset: Now try to find a subset by considering the current index element as part of subset. As the current element(arr[ind]) is included, the remaining target sum will be target - arr[ind]. Therefore, make a function call of f(ind-1,target-arr[ind]).

Note: Consider the current element in the subset only when the current element is less or equal to the target.

/*It is pseudocode  and it is not tied to 
any specific programming  language*/

f(ind, target){
    not pick = f(ind-1, target)
    pick = false
    if(target <= arr[ind]{
        pick = f(ind-1, target - arr[ind])
    }
           
}
  • Return (pick || not pick): As we are looking for only one subset whose sum of elements is equal to target, if any of the one among 'pick' or 'not pick' returns true, it means the required subset is found so, return true.
/*It is pseudocode  and it is not tied to 
any specific programming  language*/

f(ind, target){
   not pick = f(ind-1, target)
    pick = false
    if(target <= arr[ind]{
        pick = f(ind-1, target - arr[ind])
    }
    return (pick || not pick)    
}
  • Base cases: There will be two base cases.
    • First, when target == 0, it means that the subset is already found from the previous steps, so simply return true.
    • Another, when ind==0, it means we are at the first element, so return arr[ind]==target. If the element is equal to the target it will return true else false.

Code:

Starting from the end of the array:
 #include <bits/stdc++.h>
using namespace std;

class Solution {
private:
    /* Function to determine if it's possible to form a subset
    with the given target sum using elements up to the current index */
    bool canFormSubset(int currentIndex, int targetSum, vector<int>& nums) {
        // Base case: If the target sum is 0, a valid subset is found
        if (targetSum == 0)
            return true;

        // Base case: If we are at the first element, check if it equals the target sum
        if (currentIndex == 0)
            return nums[0] == targetSum;

        // Option 1: Exclude the current element
        bool excludeCurrent = canFormSubset(currentIndex - 1, targetSum, nums);

        // Option 2: Include the current element (only if it does not exceed the target sum)
        bool includeCurrent = false;
        if (nums[currentIndex] <= targetSum)
            includeCurrent = canFormSubset(currentIndex - 1, targetSum - nums[currentIndex], nums);

        // Return true if either option is successful
        return excludeCurrent || includeCurrent;
    }

public:
    /* Function to check if the array can be partitioned into
    two subsets with equal sum */
    bool canPartition(vector<int>& nums) {
        int totalSum = 0;

        // Calculate the total sum of the array
        for (int num : nums) {
            totalSum += num;
        }

        // If the total sum is odd, it cannot be partitioned into two equal subsets
        if (totalSum % 2 != 0)
            return false;

        // Target sum for each subset (half of the total sum)
        int targetSum = totalSum / 2;

        // Call the helper function to check if a subset with the target sum exists
        return canFormSubset(nums.size() - 1, targetSum, nums);
    }
};

int main() {
    vector<int> nums = {2, 3, 3, 3, 4, 5};
    int n = nums.size();

    // Create an instance of the Solution class
    Solution sol;

    // Check if the array can be partitioned into two equal subsets
    if (sol.canPartition(nums))
        cout << "The array can be partitioned into two equal subsets" << endl;
    else
        cout << "The array cannot be partitioned into two equal subsets" << endl;

    return 0;
}
Starting from the beginning of the array:
class Solution {
public:
    bool recursive(vector<int>& nums, int index, int target) {
        // Base Cases
        if (target == 0) return true;
        if (index >= nums.size() || target < 0) return false;

        // Pick the number
        bool include = recursive(nums, index + 1, target - nums[index]);

        // Skip the number
        bool exclude = recursive(nums, index + 1, target);

        return include || exclude;
    }

    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);

        // If total sum is odd, cannot split equally
        if (sum % 2 != 0) return false;

        int target = sum / 2;

        return recursive(nums, 0, target);
    }
};

Complexity Analysis:

  • Time Complexity: O(2^(N)), where N is the length of the array. As, for each index, there are two possible options.
  • Space Complexity: O(N), at maximum the depth of the recursive stack can go up to N.

2️⃣ Memoization

In order to convert a recursive solution to memoization the following steps will be taken:

  • Declare a dp array of size [n][target+1]: As there are two changing parameters in the recursive solution, 'ind' and 'target'. The maximum value 'ind' can attain is (n), where n is the size of array and for 'target' only values between 0 to target. Therefore, we need 2D dp array.

    The dp array stores the calculations of subproblems. Initialize dp array with -1, to indicate that no subproblem has been solved yet.

  • While encountering an overlapping subproblem: When we come across a subproblem, for which the dp array has value other than -1, it means we have found a subproblem which has been solved before hence it is an overlapping subproblem. No need to calculate it's value again just retrieve the value from dp array and return it.
  • Store the value of subproblem: Whenever, a subproblem is encountered and it is not solved yet(the value at this index will be -1 in the dp array), store the calculated value of subproblem in the array.

Code:

#include <bits/stdc++.h>
using namespace std;

class Solution {
public:
    bool recursive(int index, int target, vector<int>& nums, vector<vector<int>>& dp) {
        // Base Cases
        if (target == 0) return true;          // Found a valid subset
        if (index >= nums.size()) return false;

        // If already computed
        if (dp[index][target] != -1) return dp[index][target];

        // Include current number if it's not bigger than target
        bool include = false;
        if (nums[index] <= target) {
            include = recursive(index + 1, target - nums[index], nums, dp);
        }

        // Exclude current number
        bool exclude = recursive(index + 1, target, nums, dp);

        return dp[index][target] = include || exclude;
    }

    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);

        // If total sum is odd, we can't partition it equally
        if (sum % 2 != 0) return false;

        int target = sum / 2;
        int n = nums.size();

        // Create memoization table
        vector<vector<int>> dp(n, vector<int>(target + 1, -1));

        return recursive(0, target, nums, dp);
    }
};

Complexity Analysis:

  • Time Complexity:O(n * target)
  • Space Complexity:O(n * target) + O(n)

3️⃣ Tabulation

In order to convert a recursive code to tabulation code, we try to convert the recursive code to tabulation and here are the steps:

  • Declare a dp array of size [n][target+1]: As there are two changing parameters in the recursive solution, 'ind' and 'target'. The maximum value 'ind' can attain is (n) and including 0 its n, where n is the size of array, for 'target' can have any values between 0 to 'target'. So we need a 2D dp array. Set its type as bool and initialize it as false.
  • Setting Base Cases in the Array: In the recursive code, our base condition was when target == 0, it means the required subset is found and we were returning true. So, set 'true' in the dp array for every index where target = 0.
    • The first row dp[0][] indicates that only the first element of the array is considered, therefore for the target value equal to arr[0], only cell with that target will be true, so explicitly set dp[0][arr[0]] =true, (dp[0][arr[0]] means that we are considering the first element of the array with the target equal to the first element itself). Please note that it can happen that arr[0]>target, so we first check it: if(arr[0]<=target) then set dp[0][arr[0]] = true.
  • Iterative Computation Using Loops: Initialize two nested for loops to traverse the dp array and following the logic discussed in the recursive approach, set the value of each cell in the 2D dp array. Instead of recursive calls, use the dp array itself to find the values of intermediate calculations.
  • Returning the answer: At last dp[n-1][target] will hold the solution after the completion of whole process, as we are doing the calculations in bottom-up manner.

Code:

#include <bits/stdc++.h>
using namespace std;

class Solution{
private:
    /* Function to check if it's possible to partition
    the array into two subsets with equal sum*/
    bool func(int ind, int target, int n, vector<int> &arr) {
        int totSum = 0;

        // Calculate the total sum of the array
        for (int i = 0; i < n; i++) {
            totSum += arr[i];
        }

        /* If the total sum is odd, it cannot
        be partitioned into two equal subsets*/
        if (totSum % 2 == 1)
            return false;
        else {
            int k = totSum / 2;

            /* Create a DP table with dimensions 
            n x k+1, initialized with false*/
            vector<vector<bool>> dp(n, vector<bool>(k + 1, false));

            /* Base case: If the target sum is 0, it 
            can be achieved by not selecting any elements*/
            for (int i = 0; i < n; i++) {
                dp[i][0] = true;
            }

            /* Initialize the first row based 
            on the first element of the array*/
            if (arr[0] <= k)
                dp[0][arr[0]] = true;

            // Fill in the DP table using bottom-up approach
            for (int ind = 1; ind < n; ind++) {
            
                for (int target = 1; target <= k; target++) {
                    // Exclude the current element
                    bool notTaken = dp[ind - 1][target];

                    /* Include the current element if
                    it doesn't exceed the target*/
                    bool taken = false;
                    if (arr[ind] <= target)
                        taken = dp[ind - 1][target - arr[ind]];

                    // Update the DP table
                    dp[ind][target] = notTaken || taken;
                }
            }
            /* The final result is in 
            the last cell of the DP table*/
            return dp[n - 1][k];
        }
    }
public:
    /* Function to check if the array can
    be partitioned into two equal subsets*/
    bool equalPartition(int n, vector<int>& arr) {
        int totSum = 0;

        // Calculate the total sum of the array
        for (int i = 0; i < n; i++) {
            totSum += arr[i];
        }

        /* If the total sum is odd, it cannot
        be partitioned into two equal subsets*/
        if (totSum % 2 == 1)
            return false;
            
        else{
            int k = totSum / 2;
            
            // Return the result
            return func(n - 1, k, n, arr);
        }
    }
};
int main() {
    vector<int> arr = {2, 3, 3, 3, 4, 5};
    int n = arr.size();
    
    //Create an instance of Solution class
    Solution sol;

    if (sol.equalPartition(n, arr))
        cout << "The Array can be partitioned into two equal subsets";
    else
        cout << "The Array cannot be partitioned into two equal subsets";

    return 0;
}

Complexity Analysis:

  • Time Complexity:O(n*target)
  • Space Complexity:O(n*target)

4️⃣ Space Optimized Approach

Steps to space optimize the tabulation approach:

  • Decalre an array to store the previous row of the DP table. Initialize the first row and first column of the DP table based on the base conditions discussed in tabulation.
  • Now, initiate two nested loops in bottom-up approach. The first loop will run for 'ind' and the second loop will run for the 'target' variable.
  • In order to store the current row of the DP table, initialize a new array 'cur' and fill the first cell to 'true' for base condition.
  • Do the calculations for 'cur' row by performing the pick/non-pick technique used in previous approaches.
  • Now, update the previous row with the current row for the next iteration and at last return the final result stored in the last cell of the previous row(prev[k]).

Code:

#include <bits/stdc++.h>
using namespace std;

class Solution{
private:
    /* Function to check if it's possible to partition
    the array into two subsets with equal sum*/
    bool func(int n, vector<int> &arr) {
        int totSum = 0;

        // Calculate the total sum of the array
        for (int i = 0; i < n; i++) {
            totSum += arr[i];
        }

        /* If the total sum is odd, it cannot
        be partitioned into two equal subsets*/
        if (totSum % 2 == 1)
            return false;
        else {
            int k = totSum / 2;

            /* Create a vector to represent 
            the previous row of the DP table*/
            vector<bool> prev(k + 1, false);

            /* Base case: If the target sum is 0, it can
            be achieved by not selecting any elements*/
            prev[0] = true;

            /* Initialize the first row based 
            on the first element of the array*/
            if (arr[0] <= k)
                prev[arr[0]] = true;

            /* Fill in the DP table
            using a bottom-up approach*/
            for (int ind = 1; ind < n; ind++) {
                /* Initialize a vector to represent 
                the current row of the DP table*/
                vector<bool> cur(k + 1, false);
                cur[0] = true;

                for (int target = 1; target <= k; target++) {
                    // Exclude the current element
                    bool notTaken = prev[target];

                    /* Include the current element 
                    if it doesn't exceed the target*/
                    bool taken = false;
                    if (arr[ind] <= target)
                        taken = prev[target - arr[ind]];

                    // Update the current row of the DP table
                    cur[target] = notTaken || taken;
                }

                /* Set the current row as the 
                previous row for the next iteration*/
                prev = cur;
            }
            /* The final result is in the last cell
            of the previous row of the DP table*/
            return prev[k];
        }
    }
public:
    /* Function to check if the array can
    be partitioned into two equal subsets*/
    bool equalPartition(int n, vector<int>& arr) {
            
        // Return the result
        return func(n, arr);
    }
};
int main() {
    vector<int> arr = {2, 3, 3, 3, 4, 5};
    int n = arr.size();
    
    //Create an instance of Solution class
    Solution sol;

    if (sol.equalPartition(n, arr))
        cout << "The Array can be partitioned into two equal subsets";
    else
        cout << "The Array cannot be partitioned into two equal subsets";

    return 0;
}

Complexity Analysis:

  • Time Complexity:O(N*target), where target is the total sum of elements of the array divided by 2. As, here are three nested loops that account for O(N*target) complexity.
  • Space Complexity: O(target), As arrays of size (target+1) is used.