【每日一题见微知著】位标记法——好子集的数目

该博客讨论了一道关于寻找数组中好子集(子集中所有元素乘积为不同质数的乘积)的问题。博主介绍了如何使用位标记和动态规划(DP)方法解决这个问题,其中涉及到对30以内质数的标记,以及如何检查子集中的质数不重合。通过遍历数组并更新有效组合,博主得出最终答案。文章适合对算法和质数分解有兴趣的读者。

1994. 好 子 集 的 数 目 \textcolor{Red}{1994. 好子集的数目} 1994.

给你一个整数数组 nums 。如果 nums 的一个子集中,所有元素的乘积可以表示为一个或多个 互不相同的质数 的乘积,那么我们称它为 好子集

  • 比方说,如果

    nums = [1, 2, 3, 4]
    

    • [2, 3][1, 2, 3][1, 3] 子集,乘积分别为 6 = 2*36 = 2*33 = 3
    • [1, 4][4] 不是 子集,因为乘积分别为 4 = 2*24 = 2*2

请你返回 nums 中不同的 子集的数目对 109 + 7 取余 的结果。

nums 中的 子集 是通过删除 nums 中一些(可能一个都不删除,也可能全部都删除)元素后剩余元素组成的数组。如果两个子集删除的下标不同,那么它们被视为不同的子集

根据数据限制条件——nums[i]在【1,30】,主要小于32,说明可以使用32位标记

  • 30以内的每个质数使用一个独立的位来唯一标记,比如——2(…01)、3(…010)…对于2*3=6利用如下表示(…011)表示使用2和3两个质数相乘得到。
  • 特别的对于1,由于1不属于质数,且1与其他相乘不改变状态,于是将其表示为(…00)全零
  • 对于题目要求的好子集,说明子集中每个位标记不重合,重合说明出现分解为两个相同质数的情况

然后基本解题思路在利用DP思想:对于i之前的所有有效组合(即之前所有可能的好子集的乘积的统计),利用i的位标记,查找不冲突的乘积组合,然后累加添加i的组合,同时保留原组合(也可以不添加i)

class Solution {
    public int numberOfGoodSubsets(int[] nums) {
        //mask保存1~30位标记,1-0,2-01,3-010,4-(-1)由于4包含重复质数...
        int[] mask=new int[31];
        int MOD=1000000007;
        int cur=0;
        for(int i=2;i<=30;i++){
            for(int k=2;k<i;k++){
                if(i%k==0){
                    //如果被可分解为不同质数的数分解,且分解后的数包含的质数不重合
                	//比如30=2*15(15=3*5与2不重合)-》30=2*3*5(...000111)
                    if(mask[i/k]!=-1&&mask[k]!=-1&&(mask[i/k]&mask[k])==0){
                        mask[i]=mask[i/k]|mask[k];
                    }
                    else{
                        mask[i]=-1;
                    }
                    break;
                }
            }
            //如果为一个质数,赋予一个更高位位标记
            //2-..01,3-..010,5-..0100,7-...
            if(mask[i]==0){
                mask[i]=1<<cur;
                cur++;
            }
        }
        Arrays.sort(nums);
        Map<Integer,Integer> ans=new HashMap<>();
        ans.put(0,1);
        for(int i:nums){
            if(mask[i]!=-1){
                Set<Integer> k=ans.keySet();
                List<Integer> keys=new ArrayList<>();
                keys.addAll(k);
                for(int key:keys){
                    if((key&mask[i])==0){
                        int mid=key|mask[i];
                        ans.put(mid,(ans.get(key)+ans.getOrDefault(mid,0))%MOD);
                    }
                }
            }
        }
        int res=0;
        for(Map.Entry<Integer,Integer> e:ans.entrySet()){
            if(e.getKey()!=0)
                res=(res+e.getValue())%MOD;
        }
        return res;
    }
}

结尾

题目来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems

⭐️关注作者,带你刷题,从简单的算法题了解最常用的算法技能(寒假每日一题)
⭐️关注作者刷题——简单到进阶,让你不知不觉成为无情的刷题机器,有问题请私信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码之狐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值