We're given an array and need to split every element into exactly k groups where each group has the same sum. Every element must belong to exactly one group, and no group can be empty.
If the total sum of the array isn't divisible by k, the answer is immediately false. If it is divisible, each subset must sum to totalSum / k. So the problem reduces to: can we partition all elements into k subsets, each summing to a fixed target?
This is a partitioning problem, which belongs to the NP-complete family, so there is no known polynomial-time algorithm for it. The constraint nums.length <= 16 is what makes it tractable here. With at most 16 elements, exponential approaches like backtracking or bitmask DP run fast enough. The work is in choosing one of those approaches and pruning it well enough to pass within time limits.
1 <= nums.length <= 16 - With n up to 16, there are 2^16 = 65,536 possible subsets. That bound is small enough for bitmask DP and for backtracking with pruning.1 <= k <= nums.length - When k equals n, every element must be its own subset, which forces all elements to be equal.1 <= nums[i] <= 10^4 - The total sum stays at most 16 * 10^4 = 160,000, which fits comfortably in a 32-bit integer, so there is no overflow concern.Model the problem as k buckets, each needing to hold a sum of target = totalSum / k. We place numbers into buckets one at a time. If a number fits in a bucket (adding it does not exceed target), we place it and move to the next number. If no bucket can hold the current number, we backtrack and undo the previous placement.
A plain version of this explores k^n placements, too many even for n = 16. Pruning is what makes it practical. Four pruning rules cut the search space sharply:
if (buckets[i] == 0) break;.break also handles the case where the number was placed in an empty bucket but the recursion failed. Backing out and trying a different empty bucket would explore an identical subtree, so the loop stops there too.Backtracking is fast in practice, but its worst case is O(k^n) and the actual runtime depends on how well the pruning rules fit a given input. The next approach uses bitmask dynamic programming, which trades extra memory for a fixed O(n * 2^n) bound that does not depend on pruning.
Instead of filling buckets one number at a time, we represent which numbers have been used so far as a bitmask. With n <= 16 numbers, a 16-bit integer encodes any subset of numbers.
The state is the bitmask of used numbers, and for each state we store whether it can be reached such that every completed subset along the way summed to exactly target. The running sum of used elements, taken modulo target, tells us how full the current bucket is. When that sum crosses a multiple of target, one bucket is complete and the next one starts, so there is no need to track a bucket index separately.
The guard bucketFill + nums[j] <= target is what enforces the invariant. Let s = subsetSum[mask]. Since every reachable state was built by additions that never pushed the current bucket past target, the completed buckets each total exactly target, and s % target is precisely the fill level of the in-progress bucket. An addition that keeps bucketFill + nums[j] <= target either stays within the current bucket or exactly completes it (s % target returns to 0), so the invariant holds for the new state by induction. When the full mask is reached, s equals the total sum, every bucket closed at exactly target, and a valid partition exists.