Partition A Set Into Two Subsets With Minimum Absolute Sum Difference (DP on Subsequences)
This chapter explores how to partition an array of non-negative integers into two subsets such that the absolute difference between their sums is minimized. Building directly on the Subset Sum Problem (DP-14), this technique demonstrates how a reachability table generated for subsequences can be repurposed to solve complex optimization problems.
1. Problem Statement & Overview
Given an array of non-negative integers, partition the elements into two subsets—Subset 1 () and Subset 2 ()—such that every element belongs to exactly one subset.
Let the sum of elements in be and the sum of elements in be . We want to find the minimum possible absolute difference:
Example Walkthrough
Consider the array: [1, 2, 3, 4] (Total Sum = 12)
2. Prerequisite Review: The Subset Sum Problem (DP-14)
Before solving the partition problem, recall how we determined if a target sum could be formed using a subset of an array.
Core Recurrence Relationship (Pick / Non-Pick)
For any index and target, we evaluate two choices:
- Non-Take: Skip the current element, moving to the previous index with the same target.
- Take: Include the current element if it does not exceed the target, subtracting its value from the target.
Tabulation Matrix Meaning
When we build the 2D tabulation table dp[index][target], the boolean value at any cell signifies:
Note:
dp[i][t]istrueif a subset sum oftcan be formed using a prefix of the array from index0toi. Otherwise, it isfalse.
Consequently, the last row of our DP table (dp[n-1][0 ... totalSum]) tells us definitively every single target sum that can be achieved using the entire array.
3. Core Mental Model & Reduction Strategy
Instead of independently searching for both and , we can simplify the search space:
Real-Life Analogy: Imagine you have a bag of items with a known total weight (Total Sum). If you decide to pack a specific combination of items into your backpack (), you don't need to calculate what's left in the pile separately—whatever remains automatically forms the second pile ().
Mathematically:
- Let (where is a possible subset sum).
- Since , we can express as:
- The absolute difference becomes:
Thus, if we know all possible values of that can be formed by the array, we can evaluate for each valid and find the minimum!
4. Range of and Optimization
What are the possible values for ?
- Minimum value:
0(when is empty, meaning we pick no elements). - Maximum value:
Total Sum(when contains all array elements).
Symmetry Optimization
Because partition differences mirror each other (e.g., choosing subset sums yields the exact same absolute difference set as ), we only need to check values of up to .
5. Step-by-Step Algorithm Walkthrough
- Calculate Total Sum: Find the sum of all elements in the input array.
- Generate Subset Sum DP Table: Run the standard Subset Sum tabulation for target range
[0 ... TotalSum]. - Inspect the Last Row: Look at
dp[n-1][i]for all . - Evaluate Differences: If
dp[n-1][i] == true, calculate:
- Track Minimum: Keep a running minimum of these absolute differences.
6. Complete C++ Implementation
Approach 1: Tabulation with 2D DP Table
#include <bits/stdc++.h>
using.namespace std;
int minSubsetSumDifference(vector<int>& arr, int n) {
int totalSum = 0;
for(int i = 0; i < n; i++) {
totalSum += arr[i];
}
int k = totalSum;
// dp[i][target] table for subset sum
vector<vector<bool>> dp(n, vector<bool>(k + 1, false));
// Base Case 1: Target 0 is always possible (empty subset)
for(int i = 0; i < n; i++) {
dp[i][0] = true;
}
// Base Case 2: First element can form a target equal to its own value
if(arr[0] <= k) {
dp[0][arr[0]] = true;
}
// Fill the tabulation table
for(int ind = 1; ind < n; ind++) {
for(int target = 1; target <= k; target++) {
bool notTaken = dp[ind - 1][target];
bool taken = false;
if(arr[ind] <= target) {
taken = dp[ind - 1][target - arr[ind]];
}
dp[ind][target] = notTaken || taken;
}
}
// Evaluate all possible sum1 from 0 to totalSum / 2
int mini = 1e9;
for(int s1 = 0; s1 <= totalSum / 2; s1++) {
// Check if s1 is a valid subset sum using the last row of dp table
if(dp[n - 1][s1] == true) {
int s2 = totalSum - s1;
mini = min(mini, abs(s2 - s1));
}
}
return mini;
}
7. Space Optimization
Notice that when filling row ind in our tabulation, we only ever reference values from the previous row (ind - 1). We can optimize our space complexity from down to by maintaining only a single 1D boolean array (prev).
Space-Optimized Implementation
#include <bits/stdc++.h>
using namespace std;
int minSubsetSumDifferenceOptimized(vector<int>& arr, int n) {
int totalSum = 0;
for(int i = 0; i < n; i++) {
totalSum += arr[i];
}
vector<bool> prev(totalSum + 1, false);
// Base cases
prev[0] = true;
if(arr[0] <= totalSum) {
prev[arr[0]] = true;
}
// Iterating through elements
for(int ind = 1; ind < n; ind++) {
vector<bool> curr(totalSum + 1, false);
curr[0] = true;
for(int target = 1; target <= totalSum; target++) {
bool notTaken = prev[target];
bool taken = false;
if(arr[ind] <= target) {
taken = prev[target - arr[ind]];
}
curr[target] = notTaken || taken;
}
prev = curr; // Move current row to previous
}
// Find minimum absolute difference using prev row
int mini = 1e9;
for(int s1 = 0; s1 <= totalSum / 2; s1++) {
if(prev[s1] == true) {
int s2 = totalSum - s1;
mini = min(mini, abs(s2 - s1));
}
}
return mini;
}
8. Complexity Analysis
Note: An extra linear scan is performed at the end to evaluate valid subset partitions up to .
Summary and Key Takeaways
- Reduction to Subset Sum: Partition problems involving subset difference can be transformed directly into standard subset sum reachability checks.
- Single Subset Tracking: We only need to compute possible sums for one subset (), as the second subset () is implicitly defined by .
- Symmetry Halving: Checking subset sums up to is sufficient because differences repeat symmetrically beyond the halfway mark.
- Last Row Inspection: The final row of a subset sum DP table reveals all achievable subset sums for the complete array.
- Space Optimization Principle: Because state transitions only depend on the previous row, memory can be compressed from matrix storage to a rolling array.