给定一个整数数组 nums
和一个正整数 k
,找出是否有可能把这个数组分成 k
个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4 输出: True 说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
示例 2:
输入: nums = [1,2,3,4], k = 3 输出: false
提示:
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000
- 每个元素的频率在
[1,4]
范围内
方法一:DFS + 剪枝
解法和 473. 火柴拼正方形 相同。
方法二:状态压缩 + 记忆化搜索
记当前数字被划分的情况为
记当前子集的和为
- 若
$t+nums[i]>s$ ,说明第$i$ 个数字不能被添加到当前子集中,由于我们对$nums$ 数组进行升序排列,因此从$nums$ 从第$i$ 个数字开始的所有数字都不能被添加到当前子集,直接返回$false$ 。 - 否则,将第
$i$ 个数字添加到当前子集中,状态变为$state \ |\ (1<<i)$ ,继续对未划分的数字进行搜索。
注:若
class Solution:
def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
s = sum(nums)
target, m = divmod(s, k)
if m != 0:
return False
cur = [0] * k
n = len(nums)
def dfs(i: int) -> bool:
if i == n:
return True
for j in range(k):
if j > 0 and cur[j - 1] == cur[j]:
continue
cur[j] += nums[i]
if cur[j] <= target and dfs(i + 1):
return True
cur[j] -= nums[i]
return False
nums.sort(reverse=True)
return dfs(0)
class Solution:
def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
@cache
def dfs(state, t):
if state == (1 << len(nums)) - 1:
return True
for i, v in enumerate(nums):
if (state & (1 << i)):
continue
if t + v > s:
break
if dfs(state | (1 << i), (t + v) % s):
return True
return False
s, mod = divmod(sum(nums), k)
nums.sort()
if mod:
return False
return dfs(0, 0)
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
int sum = (int) Arrays.stream(nums).sum();
if (sum % k != 0) {
return false;
}
Arrays.sort(nums);
int low = 0, high = nums.length - 1;
while (low < high) {
int temp = nums[low];
nums[low] = nums[high];
nums[high] = temp;
low++;
high--;
}
return dfs(nums, new int[k], 0, sum / k);
}
private boolean dfs(int[] nums, int[] cur, int i, int target) {
if (i == nums.length) {
return true;
}
for (int j = 0; j < cur.length; j++) {
if (j > 0 && cur[j - 1] == cur[j]) {
continue;
}
cur[j] += nums[i];
if (cur[j] <= target && dfs(nums, cur, i + 1, target)) {
return true;
}
cur[j] -= nums[i];
}
return false;
}
}
func canPartitionKSubsets(nums []int, k int) bool {
sum := 0
for _, num := range nums {
sum += num
}
if sum%k != 0 {
return false
}
var (
target = sum / k
cur = make([]int, k)
n = len(nums)
)
var dfs func(i int) bool
dfs = func(i int) bool {
if i == n {
return true
}
for j := 0; j < k; j++ {
if j > 0 && cur[j-1] == cur[j] {
continue
}
cur[j] += nums[i]
if cur[j] <= target && dfs(i+1) {
return true
}
cur[j] -= nums[i]
}
return false
}
sort.Sort(sort.Reverse(sort.IntSlice(nums)))
return dfs(0)
}
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % k != 0) return false;
int target = sum / k;
int n = nums.size();
vector<int> cur(k, 0);
function<bool(int)> dfs;
dfs = [&](int i) {
if (i == n) return true;
for (int j = 0; j < k; ++j) {
if (j > 0 && cur[j - 1] == cur[j]) continue;
cur[j] += nums[i];
if (cur[j] <= target && dfs(i + 1)) return true;
cur[j] -= nums[i];
}
return false;
};
sort(nums.begin(), nums.end(), greater<int>());
return dfs(0);
}
};