def canPartitionKSubsets(nums: List[int], k: int) -> bool:
buckets = [0]*k # initialize each bucket sum to 0
target = sum(nums) // k
# We want to try placing larger numbers first
nums.sort(reverse=True)
# dfs determines which bucket to put the 'current element' (nums[idx]) into
# it is dfs as we try all possible combination for one move before testing another move
def dfs(idx):
# If we've placed all of the items, we're done
if idx == len(nums):
return set(buckets) == set([target]) # if each bucket value is target return True otherwise False
# For the current element iterate over each bucket
for i in range(k):
# Add the current element to it
buckets[i] += nums[idx]
# If it's a valid placement we now try to place the remaining elements starting with the next one.
# If we can obtain a correct partition with this move return True
if buckets[i] <= target and dfs(idx + 1):
return True
# Otherwise remove the current element from the ith bucket and try the next one.
buckets[i] -= nums[idx]
# if this bucket was originally empty and we could not place this element in it we do not need
# to test next buckets as they are also empty (it's an iterative process over the buckets)
if buckets[i] == 0:
break
# If we couldn't place the current element anywhere that leads to a valid solution
# we will need to backtrack and try something else with precedent elements.
return False
# Start by trying to place first element ie nums[0]
return dfs(0)