πŸš€Day 15. Partition Equal Subset Sum 🧠

The problem can be found at the following link: Question Link

πŸ’‘ Problem Description:

Given an array arr[], determine if it can be partitioned into two subsets such that the sum of elements in both parts is the same.

Each element must be in exactly one subset.

πŸ” Example Walkthrough:

Example 1:

Input:

arr = [1, 5, 11, 5]

Output:

True

Explanation:

The array can be partitioned into [1, 5, 5] and [11], both having the sum 11.

Example 2:

Input:

arr = [1, 3, 5]

Output:

False

Explanation:

There is no way to split the array into two subsets with equal sum.

Constraints:

  • $(1 \leq arr.size \leq 100)$

  • $(1 \leq arr[i] \leq 200)$

🎯 My Approach:

Optimized 1D Dynamic Programming

Algorithm Steps:

  1. Calculate the total sum of the array. If it's odd, return false (cannot be evenly split).

  2. Use a DP table (dp[j]), where dp[j] tells if we can form sum j.

  3. Initialize dp[0] = true, since a sum of 0 is always possible.

  4. Iterate through each element in the array:

    • Update dp[j] = dp[j] OR dp[j - num] for j = target down to num.

  5. If dp[target] is true, we can partition the array into two subsets with equal sum.

πŸ•’ Time and Auxiliary Space Complexity

  • Expected Time Complexity: $O(N \times sum)$, as we iterate over the elements and process sums up to sum/2.

  • Expected Auxiliary Space Complexity: $O(sum)$, as we use a 1D DP array of size sum/2 + 1.

πŸ“ Solution Code

Code (C++)

class Solution {
  public:
    bool equalPartition(vector<int>& arr) {
        int sum = accumulate(arr.begin(), arr.end(), 0);
        if (sum % 2) return false;
        int target = sum / 2;
        vector<bool> dp(target + 1, false);
        dp[0] = true;
        for (int num : arr)
            for (int j = target; j >= num; --j)
                dp[j] = dp[j] || dp[j - num];
        return dp[target];
    }
};
⚑ Alternative Approaches

2️⃣ Dynamic Programming (O(NΓ—sum) Time, O(NΓ—sum) Space) β€” 2D DP

Algorithm Steps:

  1. Use a 2D DP table, where dp[i][j] represents whether a subset with sum j can be formed using the first i elements.

  2. Base Case:

    • dp[0][0] = true (sum 0 can be formed with zero elements).

    • dp[i][0] = true for all i (sum 0 is always achievable).

  3. Recurrence Relation: $[ dp[i][j] = dp[i-1][j] \quad \text{(exclude element)} $] If arr[i-1] ≀ j, then: $[ dp[i][j] = dp[i-1][j] \text{ OR } dp[i-1][j - arr[i-1]] $] (include element if possible).

class Solution {
  public:
    bool equalPartition(vector<int>& arr) {
        int sum = accumulate(arr.begin(), arr.end(), 0);
        if (sum % 2) return false;
        int target = sum / 2;
        int n = arr.size();
        vector<vector<bool>> dp(n + 1, vector<bool>(target + 1, false));
        for (int i = 0; i <= n; i++) dp[i][0] = true;

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= target; j++) {
                dp[i][j] = dp[i-1][j];
                if (j >= arr[i-1]) dp[i][j] = dp[i][j] || dp[i-1][j - arr[i-1]];
            }
        }
        return dp[n][target];
    }
};

βœ… Time Complexity: O(N Γ— sum) βœ… Space Complexity: O(N Γ— sum)

3️⃣ Recursive + Memoization (O(NΓ—sum) Time, O(NΓ—sum) Space)

Algorithm Steps:

  1. Define a recursive function canPartition(i, target) that checks if a subset of arr[0...i] sums up to target.

  2. Base Cases:

    • If target == 0, return true.

    • If i < 0 or target < 0, return false.

  3. Recurrence Relation:

    • Exclude arr[i]: canPartition(i - 1, target).

    • Include arr[i] if arr[i] ≀ target: canPartition(i - 1, target - arr[i]).

  4. Use Memoization (dp[i][target]) to avoid redundant calculations.

class Solution {
  public:
    vector<vector<int>> dp;
    bool solve(vector<int>& arr, int i, int target) {
        if (target == 0) return true;
        if (i < 0 || target < 0) return false;
        if (dp[i][target] != -1) return dp[i][target];
        return dp[i][target] = solve(arr, i - 1, target) || solve(arr, i - 1, target - arr[i]);
    }

    bool equalPartition(vector<int>& arr) {
        int sum = accumulate(arr.begin(), arr.end(), 0);
        if (sum % 2) return false;
        int target = sum / 2, n = arr.size();
        dp.assign(n, vector<int>(target + 1, -1));
        return solve(arr, n - 1, target);
    }
};

βœ… Time Complexity: O(N Γ— sum) βœ… Space Complexity: O(N Γ— sum) (recursion stack)

Comparison of Approaches

Approach

⏱️ Time Complexity

πŸ—‚οΈ Space Complexity

βœ… Pros

⚠️ Cons

1D DP (Space Optimized)

🟑 O(N Γ— sum)

🟒 O(sum/2)

Best space-efficient solution

Requires careful indexing

2D DP (Tabulation)

🟑 O(N Γ— sum)

πŸ”΄ O(N Γ— sum)

Intuitive approach

High space usage

Recursive + Memoization

🟑 O(N Γ— sum)

πŸ”΄ O(N Γ— sum)

Natural recursion flow

Stack overhead

βœ… Best Choice?

  • If optimizing space: Use 1D DP (Space-Optimized).

  • If space is not a concern: Use 2D DP (Tabulation) for easier understanding.

  • For recursion lovers: Use Recursive + Memoization.

Code (Java)

class Solution {
    static boolean equalPartition(int[] arr) {
        int sum = Arrays.stream(arr).sum();
        if (sum % 2 != 0) return false;
        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for (int num : arr)
            for (int j = target; j >= num; --j)
                dp[j] |= dp[j - num];
        return dp[target];
    }
}

Code (Python)

class Solution:
    def equalPartition(self, arr):
        s = sum(arr)
        if s % 2: return False
        target, dp = s // 2, [False] * (s // 2 + 1)
        dp[0] = True
        for num in arr:
            for j in range(target, num - 1, -1):
                dp[j] |= dp[j - num]
        return dp[target]

🎯 Contribution and Support:

For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: Any Questions. Let’s make this learning journey more collaborative!

⭐ If you find this helpful, please give this repository a star! ⭐


πŸ“Visitor Count

Last updated