698.划分为k个相等的子集(C#)

个人笔记,仅学习交流用

题目:

给定一个整数数组  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] 范围内

解法:

首先知道输入有两个,一个是数组nums,一个是k,要将数组nums内的数分成k个总和相等的子集,则每个子集的总和应该为nums.Sum()/k,这个数字应该为整数,因此如果nums.Sum()%k不等于0,则必然不可能分成k个相等子集,求出ave=nums.Sum()/k。

之后我们要取nums中的任意位数,使其之和为ave,因为len(nums)<=16,因此可以使用一个整数S来表达当前nums数字的使用情况,用二进制来表示,令1为未使用,0为使用,这种方法叫做状态压缩动态规划,简称状压DP,例如示例1:

nums=[4,3,2,3,5,2,1],k=4,则ave=5,初始状态下其S=1111111(二进制,7个1,代表7个数字都未被使用,最右边代表第一位,这里通过位运算符(1<<n)-1来实现),为了让每种状态只被遍历一次,我们创建bool[] dp,令dp=false时跳过,每当遍历到一个新的状态后,令dp=false,dp的大小为1<<n,dp默认全为true。

第一步,【遍历】S中不为0的位,通过((s>>i)&1)!=0来判断对应位数是否为1,如取第一位,S=1111110(S=S^(1<<i),代表取走第i位),则可以得到p=nums[0]+p。p代表当前这个子集已经存储的数的总和,初始为0,p始终<=ave,因此如果p>ave,则跳过这一位。如果p=ave,则重置p=0,因此令p=(num[i]+p)%ave。

取完一位之后,我们便把问题从:

S=1111111,p=0

变成:

S=1111110,p=4

我们通过递归再取出S=1111110中的一位数,再次计算p,以此反复,直到最后求出S=0000000时返回true,如果不存在S=0000000情况,则再遍历完所有之后返回false

以下是代码:

public class Solution {
    int[] nums;
    int ave, n;
    bool[] dp;

    public bool CanPartitionKSubsets(int[] nums, int k) {
        this.nums = nums;
        int all = nums.Sum();
        if (all % k != 0) {
            return false;
        }
        ave = all / k;
        Array.Sort(nums);
        n = nums.Length;
        if(nums[n-1]>ave) return false;
        dp=new bool[1<<n];
        Array.Fill(dp,true);
        return DFS((1<<n)-1,0);
    }

    public bool DFS(int s, int p) {
        if(s==0)return true;
        if(!dp[s])return false;
        dp[s]=false;
        for(int i=0;i<n;i++){
            if(nums[i]+p>ave)break;
            if(((s>>i)&1)!=0){
                if(DFS(s^(1<<i),(p+nums[i])%ave))return true;
            }
        }
        return false;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值