Patterns/Part VIII - Cross-Topic Deep Dives/Balanced DP

Pattern Reference

Balanced DP

"DP with equilibrium constraints. Balance brackets, load balancing, fair division, partition with balance target."

Loading...

Deep Dive Tutorial

Balanced partition problems come in several flavors: (1) two-way split minimizing difference → subset sum DP to target sum/2; (2) k-way split minimizing maximum group sum → binary search + greedy; (3) equal-sum k groups → bitmask DP tracking which elements are used; (4) split into groups with size constraints → DP with group size tracking. The key: identify what "balanced" means and choose the matching technique.

Balanced partition templates
// 1. Two-way split: minimize |S1 - S2|
function minDiffPartition(nums) {
    const total = nums.reduce((a,b)=>a+b,0);
    // Bitset DP: which sums ≤ total/2 are achievable?
    let dp = 1n; // BigInt bitset
    for (const n of nums) dp |= dp << BigInt(n);
    const half = total >> 1;
    for (let s = half; s >= 0; s--)
        if ((dp >> BigInt(s)) & 1n) return total - 2 * s;
    return total; // shouldn't reach
}

// 2. k-way split: minimize maximum group sum (binary search + greedy)
function minMaxPartition(nums, k) {
    let lo = Math.max(...nums), hi = nums.reduce((a,b)=>a+b,0);
    const check = (limit) => {
        let groups = 1, cur = 0;
        for (const n of nums) {
            if (cur + n > limit) { groups++; cur = 0; }
            cur += n;
        }
        return groups <= k;
    };
    while (lo < hi) {
        const mid = (lo + hi) >> 1;
        check(mid) ? hi = mid : lo = mid + 1;
    }
    return lo;
}

// 3. k groups of equal sum (bitmask DP for small n)
function canPartitionKSubsets(nums, k) {
    const total = nums.reduce((a,b)=>a+b,0);
    if (total % k !== 0) return false;
    const target = total / k;
    const n = nums.length;
    const dp = new Array(1 << n).fill(false); dp[0] = true;
    const sums = new Array(1 << n).fill(0);
    for (let mask = 0; mask < (1 << n); mask++) {
        if (!dp[mask]) continue;
        for (let i = 0; i < n; i++) {
            if ((mask >> i) & 1) continue;
            const next = mask | (1 << i);
            const newSum = (sums[mask] + nums[i]) % target;
            if (sums[mask] + nums[i] <= target) {
                sums[next] = newSum;
                dp[next] = true;
            }
        }
    }
    return dp[(1 << n) - 1];
}

Worked Problems

scale
Balanced partition decision tree:
- 2 groups, min diff: subset-sum DP to target=sum/2, O(n × sum)
- k groups, min max: binary search + greedy, O(n log sum)
- k groups, equal sum, small n (≤20): bitmask DP, O(2^n × n)
- k groups, equal size: sort + pair, or DP by sorted order

Bitmask DP tip: For "can we partition into k equal-sum groups," track partial current bucket sum mod target. An element adds to the current bucket or starts a new one when bucket fills.