You are given an array of positive integers and an integer k. Your task is to determine whether it's possible to divide the array into exactly k non-empty groups where each group has the same sum.
Each element must belong to exactly one group, and all elements must be used.
true if the array can be partitioned into k equal-sum subsetsfalse otherwisek subsets must be non-emptyExample 1:
Input: nums = [4, 3, 2, 3, 5, 2, 1], k = 4 Output: true Explanation: It's possible to divide into 4 subsets with equal sum 5: [5], [1, 4], [2, 3], [2, 3]
Example 2:
Input: nums = [1, 2, 3, 4], k = 3 Output: false Explanation: The total sum is 10, which cannot be evenly divided by 3.
Example 3:
Input: nums = [2, 2, 2, 2, 3, 4, 5], k = 4 Output: true Explanation: One valid partition is [5], [4], [2, 2], [2, 3], each with sum 5.
Hint 1: Initial Checks Before attempting to partition, verify two necessary conditions:
- Is the total sum divisible by k?
- Is any single element larger than the target sum per subset?
If either check fails, return false immediately.
Hint 2: Search Strategy Think about this as filling k buckets to a target capacity. You can use backtracking to try placing each number into different buckets. Consider sorting the array in descending order first - this helps fail faster when a configuration is impossible.
Hint 3: State Tracking Use a visited array or bitmask to track which elements have been assigned to subsets. When building subsets, once a subset reaches the target sum, start building the next one from scratch with the remaining elements.
Full Solution `` Approach:
This solution uses backtracking with aggressive pruning to explore the search space efficiently:
Initial validation: Check if the total sum is divisible by k and no element exceeds the target sum per subset.
Sorting optimization: Sort the array in descending order. This allows us to fail faster when a configuration is impossible, as larger numbers are more constraining.
Backtracking strategy: We fill k buckets sequentially. For each bucket, we try adding unused elements until we reach the target sum, then move to the next bucket.
Key pruning techniques:
- Skip elements that would exceed the target sum
- When starting a new bucket, if the first element we try doesn't lead to a solution, no other element will either (due to sorting)
- Skip duplicate values to avoid redundant exploration
State tracking: Use a boolean array to mark which elements have been assigned to subsets.
Time Complexity: O(k · 2^n) in the worst case, where n is the array length. Each element can be in or out of the current subset, and we explore k subsets.
Space Complexity: O(n) for the recursion stack and the used array.
def canPartitionKSubsets(nums: list[int], k: int) -> bool:
total = sum(nums)
# Early termination: total must be divisible by k
if total % k != 0:
return False
target = total // k
nums.sort(reverse=True) # Sort descending for faster pruning
# Early termination: no element can exceed target
if nums[0] > target:
return False
used = [False] * len(nums)
def backtrack(subset_idx, current_sum, start_idx):
# Base case: we've successfully filled k-1 subsets
# The last subset will automatically have the correct sum
if subset_idx == k:
return True
# Current subset is complete, move to next subset
if current_sum == target:
return backtrack(subset_idx + 1, 0, 0)
# Try adding each unused number to current subset
for i in range(start_idx, len(nums)):
if used[i]:
continue
# Prune: skip if adding this number exceeds target
if current_sum + nums[i] > target:
continue
# Prune: avoid duplicate work
# If we're at the start of a bucket and skip a number,
# don't try identical numbers
if i > 0 and not used[i-1] and nums[i] == nums[i-1]:
continue
# Try including this number
used[i] = True
if backtrack(subset_idx, current_sum + nums[i], i + 1):
return True
used[i] = False
# Critical pruning: if we're starting a new subset and
# this number doesn't work, no point trying other numbers
if current_sum == 0:
break
return False
return backtrack(0, 0, 0)