题目描述
给定一个整数数组 nums
和一个正整数 k
,找出是否有可能把这个数组分成 k
个非空子集,其总和都相等。
样例
输入:nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出:True
解释:有可能将其分成 4 个子集 (5),(1,4),(2,3),(2,3) 等于总和。
限制
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000
算法
(搜索剪枝) $O(n \log n + \binom{n}{k} (n - k)^k)$
- 先求出数组的总和,如果总和不是 $k$ 的倍数,则直接返回
false
。 - 求出子集和的目标值,如果数组中有数字大于这个子集和目标值,则返回
false
。 - 将数组 从大到小 排序。贪心地来看,先安排大的数字会更容易先找到答案,这是因为小的数字再后期更加灵活。
- 我们按照子集去递归填充,即维护一个当前总和
cur
,如果当前值等于了目标值,则进行下一个子集的填充。最终,如果只剩下一个子集尚未填充(无需 0 个),则可以直接返回true
。 - 还需要同时维护一个
last
,即表示当前这个子集,需要从数组的哪个位置开始尝试。 - 这里还有一个非常重要的策略,就是如果当前总和
cur
为 0 时,我们直接指定第一个未被使用的数字作为当前这个子集的第一个数字(最大的数字)。这是防止重复枚举,因为第一个未被使用的数字一定要出现在某个子集中。否则,如果我们最终没有使用这个数字,在尝试下一个集合时,还会重复尝试使用这个数字,造成大量重复计算。
时间复杂度
- 排序的时间复杂度为 $O(n \log n)$。
- 首先我们可以确定 $k$ 个子集中最大数字的位置,共有 $O(\binom{n}{k})$ 种选择。
- 剩下每个数字都有 $k$ 种选择,故理论时间复杂度上限为 $O(n \log n + \binom{n}{k} (n - k)^k)$。
- 但实际上的运行时间复杂度远比这个小。
空间复杂度
- 需要额外 $O(n)$ 的空间存储递归的系统栈和记录访问数组。
C++ 代码
class Solution {
public:
bool solve(int cur, int last, int sum, int r,
const vector<int> &nums, vector<bool> &used) {
if (r == 1) return true;
if (cur > sum) return false;
if (cur == sum) return solve(0, 0, sum, r - 1, nums, used);
for (int i = last; i < nums.size(); i++)
if (!used[i]) {
used[i] = true;
if (solve(cur + nums[i], i + 1, sum, r, nums, used))
return true;
used[i] = false;
// 如果当前正在枚举子集中的第一个数字且失败了,则直接剪枝。
if (cur == 0) return false;
}
return false;
}
bool canPartitionKSubsets(vector<int>& nums, int k) {
int n = nums.size();
int sum = 0;
for (int i = 0; i < n; i++)
sum += nums[i];
if (sum % k != 0)
return false;
sort(nums.rbegin(), nums.rend());
vector<bool> used(n, false);
return solve(0, 0, sum / k , k, nums, used);
}
};
赞! 要是想要枚举所有的解,要怎么修改一下代码呢?我自己弄了弄,总是不对,不知大佬能否指教一下👀,万分感谢!
去掉所有的 return true,在最后合法的情况下输出解就行。
但是这样就没办法剪枝了吧,因为不能回传bool值了,有没有什么办法解决这个问题啊,我这里纠结了好久诶
可以剪枝呀,不要回传 bool 值,直接 return 就行。bool 类型只是为了找到第一个解的时候直接退出。
诶,好像是这样的哦,那我去试试看!对了,还有就是,你的代码里没有对重复元素进行剪枝,要是加这个剪枝的话,好像就需要回传的bool值了吧,不然不论失败与否,都会把重复元素跳过的,这个要怎么处理呢?
这个枚举是不存在重复情况的。而且就算加了其他一些剪枝,也不需要 bool 值,递归回溯本身就不需要 bool 值,bool 值只是在找到第一个解时退出用的。
把 return true 看做 exit(0)
好的,我再想想看,太感谢啦!