【220222】1994.好子集的数目

该博客介绍了如何使用动态规划解决LeetCode中的一个问题——计算好子集的数量。好子集中每个质数最多出现一次。文章详细讲解了状态转移DP的实现,包括遍历顺序优化、剪枝策略以及编程技巧,如质数平方滤除和数字的质因数分解。通过遍历数组并更新动态规划数组f,最终计算出满足条件的子集总数。
摘要由CSDN通过智能技术生成

题目:https://leetcode-cn.com/problems/the-number-of-good-subsets/

状态转移DP

DP:dp[i][state] 好子集的数目

  • i: [2, i]范围内的数,i在30以内
  • state:10个质数的选中情况

逻辑:

  • 每个好子集里,每个质数最多出现一次
  • DP基于【对于10个质数,各有 选 or 不选 的情况】
  • 对于每个数字,确定有唯一的state
  • PD基于【对于每个元素,有 选 or 不选 的情况】
  • 选择元素的条件:state不能与i-1时の,同时为1

编程技巧:

  • 遍历顺序:不按照数组顺序,按照自然顺序再剪枝。
    【数字大小范围限定在30以内】【求总数,与顺序无关】
    剪枝1:freq数组记录某个数字的出现次数,不涉及的用0来continue
    剪枝2:元素 % 质数的平方 == 0时,不符题意,滤去
//官方题解
class Solution {
    static final int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
    static final int NUM_MAX = 30;
    static final int MOD = 1000000007;

    public int numberOfGoodSubsets(int[] nums) {
    	//遍历一遍计算数字的出现次数
        int[] freq = new int[NUM_MAX + 1];
        for (int num : nums) {
            ++freq[num];
        }

        int[] f = new int[1 << PRIMES.length];
        f[0] = 1;
        //讨论数字1,锦上添花的作用,选择了则多一倍数目
        for (int i = 0; i < freq[1]; ++i) {
            f[0] = f[0] * 2 % MOD;
        }
        
        //对于每个数字范围(从2开始)
        for (int i = 2; i <= NUM_MAX; ++i) {
            if (freq[i] == 0) {
                continue;
            }
            
            // 检查 i 的每个质因数是否均不超过 1 个
            int subset = 0, x = i;
            boolean check = true;
            //对于每个质数
            for (int j = 0; j < PRIMES.length; ++j) {
                int prime = PRIMES[j];
                //【质数的平方】为因数,则滤去
                if (x % (prime * prime) == 0) {
                    check = false;
                    break;
                }
                //数字为质数,则得到其自身的使用情况二进制编码
                //【巧妙】有没有可能 x = 质数1 * 质数2 = 质数3 * 质数4 ? 不可能!
                if (x % prime == 0) {
                    subset |= (1 << j);
                }
            }
            
            //数字为质数的平方则滤去
            if (!check) {
                continue;
            }

            // 动态规划
            for (int mask = (1 << PRIMES.length) - 1; mask > 0; --mask) {
            	//如果mask包含(i唯一对应的)subset
                if ((mask & subset) == subset) {
                //编程技巧:倒序遍历(大mask需要用到上一轮的小mask)
                    f[mask] = (int) ((f[mask] + ((long) f[mask ^ subset]) * freq[i]) % MOD);
                }
            }
        }	//对于每个数字范围

        int ans = 0;
        //不从0开始。因为单独的f[0],分解后无质数,不符题意
        for (int mask = 1, maskMax = (1 << PRIMES.length); mask < maskMax; ++mask) {
            ans = (ans + f[mask]) % MOD;
        }
        
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值