题目
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-to-k-equal-sum-subsets
分析
这个其实是一个np问题,所以他的数据也是只有16,因为我们只能做穷举的方法,使用used数组来记录元素是否用过.但是怎么穷举就有一些门道
我的主体使用了回溯算法的框架细节有很多(为了优化加速)
void backtrace(int now,int kk,int sum,vector<int>& nums,vector<bool>& used)
now是使用的元素,kk是已经完成了子集,在达到k个的时候返回.sum是现在的子集sum到了多少.used记录了哪些元素被使用了
-
对于每一个backtrace,第一句话就是
if(res==true) return;
这是为了得到答案后快速return,防止有答案了之后还在找
-
在nums提前删除一些等于per(sum/k)的项,减少后面枚举的数量,毕竟少一个就可能少一个数量级
int tmp=0; auto it=nums.begin(); while(it!=nums.end()) { if(*it==per){ it=nums.erase(it); tmp++; } else it++; }
-
质的飞跃.使用last_pos来记录枚举的位置,
如果只是使用前两种优化,那么还是会超时,前两个是我最先想到的,其实只是打酱油的,但是写都写了,还是放上了,这个质的飞跃我其实也早想到了,只不过实践来的晚一点.
这个可以防止大量的重复遍历,就是比如我 1,2,3,4,5.我使用了1,3,那么我不可能使用2,因为使用2是在1,2,的时候用的,我用到的都应该是3后面的.这里就好像是算组合数,怎样才能不重不漏,就是先选1,再选2,2的组合选完了,选3之后,1,3再选3后面的才不会重复,选不到2,这样可以在一个数组长度内选完一次子集的.所以减少了很多.
并且和used数组等回溯的架构里,tmp_pos也需要在选和不选之后更新.所以使用一个变量记录,这个结果回溯完后再改回去(当我没说,没用ast_pos=tmp_pos;也过了)if(!used[i]&&sum+nums[i]<=per) { last_pos