This problem occurred as Partitioning Souveniers Problem in the sixth week of the edX MOOC Algs200x Algorithmic Design and Techniques by the UC San Diego. We will solve it generically here in this problem.
We can use visited boolean array to track if the value is part of some subset. Then we go deeper into DFS. For each partition, targetSum is sum(nums)/k.
// C++ backtracking solution
#include<vector>#include<numeric>classSolution {
public:bool canPartitionKSubsets(std::vector<int>& nums, int k) {
int n = nums.size();
int sum = std::accumulate(nums.begin(), nums.end(), 0);
if (sum % k !=0) return false;
int target = sum / k;
std::vector<bool> used(n, false);
returndfs(nums, used, k, 0, 0, target);
}
private:bool dfs(const std::vector<int>& nums, std::vector<bool>& used, int k, int start, int currSum, int target) {
if (k ==1) return true;
if (currSum == target) return dfs(nums, used, k -1, 0, 0, target);
for (int i = start; i < (int)nums.size(); ++i) {
if (used[i]) continue;
if (currSum + nums[i] > target) continue;
used[i] = true;
if (dfs(nums, used, k, i +1, currSum + nums[i], target)) return true;
used[i] = false;
}
return false;
}
};
from typing import List
classSolution:
defcanPartitionKSubsets(self, nums: List[int], k: int) -> bool:
n = len(nums)
s = sum(nums)
if s % k !=0:
returnFalse target = s // k
used = [False] * n
defdfs(k_remain: int, start: int, curr_sum: int) -> bool:
if k_remain ==1:
returnTrueif curr_sum == target:
return dfs(k_remain -1, 0, 0)
for i in range(start, n):
if used[i] or curr_sum + nums[i] > target:
continue used[i] =Trueif dfs(k_remain, i +1, curr_sum + nums[i]):
returnTrue used[i] =FalsereturnFalsereturn dfs(k, 0, 0)
⏰ Time complexity: O(k*2^n) because we run k iterations for recursion, and in that we have 2^n choices to create the subset such that sum is equal to sum(nums)/k.
🧺 Space complexity: O(n) for visited set, recursion stack, etc
We can encode which elements are used using a bitmask and use DP to track the current modulo-sum of the partially filled subsets. This allows us to avoid the extra factor of k in the naive backtracking by reusing computed subset-fillability information for every mask.
Compute total sum and target = sum / k. If sum % k != 0, return false.
Let dp[mask] = true if the subset represented by mask can be partitioned into some number of full buckets and a residual sum less than or equal to target; we also track the current modulo-sum for that mask.
Iterate masks from 0..(1«n)-1 and try to add an unused element to extend the mask if it doesn’t overflow the target.
If dp[(1«n)-1] is reachable and completes exactly k buckets, return true.
from typing import List
classSolution:
defcanPartitionKSubsets(self, nums: List[int], k: int) -> bool:
n = len(nums)
s = sum(nums)
if s % k !=0:
returnFalse target = s // k
N =1<< n
dp = [-1] * N
dp[0] =0for mask in range(N):
if dp[mask] <0:
continuefor i in range(n):
ifnot (mask >> i) &1:
nxt = mask | (1<< i)
if dp[mask] + nums[i] <= target:
dp[nxt] = max(dp[nxt], (dp[mask] + nums[i]) % target)
return dp[N-1] ==0
classSolution {
publicbooleancanPartitionKSubsets(int[] nums, int k) {
int n = nums.length;
int sum = 0;
for (int x : nums) sum += x;
if (sum % k != 0) returnfalse;
int target = sum / k;
int N = 1 << n;
int[] dp =newint[N];
Arrays.fill(dp, -1);
dp[0]= 0;
for (int mask = 0; mask < N; ++mask) {
if (dp[mask]< 0) continue;
for (int i = 0; i < n; ++i) if ((mask & (1 << i)) == 0) {
int nxt = mask | (1 << i);
if (dp[mask]+ nums[i]<= target) {
dp[nxt]= Math.max(dp[nxt], (dp[mask]+ nums[i]) % target);
}
}
}
return dp[N-1]== 0;
}
}