The problem
You get an array nums of positive integers and a number k. Decide whether the numbers can be split into exactly k non-empty groups whose sums are all equal. Every number must be used exactly once. Constraints are small: n ≤ 16.
Input: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
Output: TrueThe total is 20, so each group must sum to 5: {5}, {4, 1}, {3, 2}, {3, 2}.
How to think about it
Start with brute force. Assign each number to one of k buckets and check whether all buckets match. That is kⁿ assignments, and most of them are the same partition with the buckets relabelled. For n = 16, k = 4 that is over four billion.
First, cheap pruning. If sum(nums) is not divisible by k, the answer is False. Otherwise every bucket must reach target = sum // k, and any number bigger than target makes it impossible.
The key insight: fill buckets one at a time, and the only thing that matters is which numbers are used. If you always fill the current bucket before opening the next, the state is the set of used numbers plus how much room is left in the current bucket. Two different orders that use the same numbers lead to the same future, so you memoise. With n ≤ 16, the set fits in a bitmask of 2¹⁶ = 65,536 values. When a bucket closes exactly (room hits 0), reset the room to target and start the next bucket.
Which pattern is this? Bitmask DP. The tell is n ≤ 16 together with “use every item exactly once”: the subset of used items is the state.
Approach
- Compute
total. Iftotal % k != 0, returnFalse. Settarget = total // k. - Sort descending, so big numbers are tried first and failures show up early. If the largest number exceeds
target, returnFalse. - Define
dp(mask, rem): can the unused numbers finish the job, givenremroom left in the current bucket? - If
maskhas every bit set, all numbers are placed: returnTrue. - Otherwise try each unused number
iwithnums[i] <= rem. Recurse with bitiset andrem - nums[i], ortargetif that closes the bucket. - Memoise with
lru_cache. The answer isdp(0, target).
Solution
from functools import lru_cache
def canPartitionKSubsets(nums, k):
total = sum(nums)
if total % k: return False
target = total // k; n = len(nums); nums.sort(reverse=True)
if nums[0] > target: return False
@lru_cache(None)
def dp(mask, rem):
if mask == (1 << n) - 1: return True # every number placed
for i in range(n):
if not mask & (1 << i) and nums[i] <= rem:
nr = rem - nums[i]
# bucket closed exactly? open a fresh one with full room
if dp(mask | (1 << i), nr if nr > 0 else target): return True
return False
return dp(0, target)
# canPartitionKSubsets([4,3,2,3,5,2,1], 4) -> TrueDry run
After sorting, nums = [5, 4, 3, 3, 2, 2, 1] and target = 5. This is the successful path the recursion takes (indices into the sorted array):
| Pick index | Value | rem before |
rem after |
Mask after |
|---|---|---|---|---|
| 0 | 5 | 5 | 0, bucket closes, reset to 5 | 0000001 |
| 1 | 4 | 5 | 1 | 0000011 |
| 6 | 1 | 1 | 0, reset to 5 | 1000011 |
| 2 | 3 | 5 | 2 | 1000111 |
| 4 | 2 | 2 | 0, reset to 5 | 1010111 |
| 3 | 3 | 5 | 2 | 1011111 |
| 5 | 2 | 2 | 0 | 1111111 (full) |
The full mask is reached, so the answer is True. Only 8 states were ever cached on this input.
Complexity
- Time: O(n·2ⁿ).
remis fully determined by the mask (it istarget - (sum of used) % target), so there are at most 2ⁿ distinct states, and each tries up tonnumbers. - Space: O(2ⁿ). For the memo table, plus O(n) recursion depth.
Edge cases and pitfalls
k = 1is alwaysTrue; the code handles it naturally becausetarget = total.- Sum not divisible by
kor one number bigger thantarget: returnFalsebefore doing any search. - Duplicates (like the two 3s) cause repeated work in plain backtracking. The memo on
maskabsorbs most of it. - Forgetting to reset
remwhen a bucket closes makes the search think the whole array is one bucket. - Greedy “biggest first into the emptiest bucket” is wrong. It can paint itself into a corner; you need search or DP.
Related problems
- Partition Equal Subset Sum (LeetCode 416): the
k = 2case, solvable with a 0/1 knapsack instead of a bitmask. - Shortest Path Visiting All Nodes (LeetCode 847): another “subset of used items” bitmask state.
- Subsets (bitmask enumeration): the basics of treating an integer as a set.
- 1.Partition to K Equal Sum Subsets (LeetCode 698)
- 2.House Robber III (LeetCode 337)
- 3.Stone Game (LeetCode 877)
- 4.Partition Array for Maximum Sum (LeetCode 1043)
- 5.Shortest Path Visiting All Nodes (LeetCode 847)
- 6.Burst Balloons (LeetCode 312)
- 7.Minimum Cost to Cut a Stick (LeetCode 1547)
- 8.Count Vowels Permutation (LeetCode 1220)
- 9.Student Attendance Record II (LeetCode 552)
- 10.Maximum Profit in Job Scheduling (LeetCode 1235)