划分为k个相等的子集

698. 划分为k个相等的子集
nums = [4, 3, 2, 3, 5, 2, 1], k = 4
将nums分为4个子集,每个子集的和相同。4个子集的和为4*某个数,就等于nums的和。因此,如果sum(nums)除以k有余数,则不能划分为k个子集,返回false。如果数组中最大的那个数大于sum(nums)/k(平均数),则这个数怎么也不能成为一个子集,因此也不能划分,返回false。
思路是因为数组长度16,可以用一个整数s的二进制表示。数组中哪个数已经被用了,哪个位就是0,否则为1。一开始为(1<<n)-1,比如n=4,1<<4=10000=16,16-1=15=01111,1111正好表示4个数都未用,每一次就用现在的数p与每个数相加,如果大于per,就不用继续往后面加了,因为后面的数一定比现在大,直接break返回false。那怎么判断现在的数已经被占用呢,让s右移这个数的位置,使这个数在整数最后面,然后与1相与就得到最后这个数,是0则占用,是1则可以加。
^表示异或,即1和1得到0,这样就把不占用变成占用了。
记录下每个s状态是否可行,就可以用记忆化搜索。
c++也有lamdba表达式。
[capture list ] ( parameter list) -> return type { function body }
function<bool(int,int)> dfs = [&](int s, int p)->bool{}

   capture list :  捕获列表, 一个lambda 所在函数中定义的局部变量的列表,通常为空
   return  type :  返回类型
   parameter list:参数列表
   function body: 函数体 
class Solution {
public:
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int all = accumulate(nums.begin(), nums.end(), 0);
        if (all % k > 0) {
            return false;
        }
        int per = all / k; 
        sort(nums.begin(), nums.end());
        if (nums.back() > per) {
            return false;
        }
        int n = nums.size();
        vector<bool> dp(1 << n, true);
        function<bool(int,int)> dfs = [&](int s, int p)->bool {
            if (s == 0) {
                return true;
            }
            if (!dp[s]) {
                return dp[s];
            }
            dp[s] = false;
            for (int i = 0; i < n; i++) {
                if (nums[i] + p > per) {
                    break;
                }
                if ((s >> i) & 1) {
                    if (dfs(s ^ (1 << i), (p + nums[i]) % per)) {
                        return true;
                    }
                }
            }
            return false;
        };
        return dfs((1 << n) - 1, 0);
    }
};

之所以可以用(p + nums[i]) % per)判断是否可以用数组组成k个总和为per的组,是因为上面已经判断了nums[i] + p > per,就限定p+现在的数只能小于等于per。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值