leetcode 1994. 好子集的数目 思维dfs或递推或二进制枚举
leetcode 1994. 好子集的数目 dfs
给你一个整数数组 nums 。如果 nums 的一个子集中,所有元素的乘积可以用若干个 互不相同的质数 相乘得到,那么我们称它为 好子集 。
比方说,如果 nums = [1, 2, 3, 4] :[2, 3] ,[1, 2, 3] 和 [1, 3] 是 好 子集,乘积分别为 6 = 23 ,6 = 23 和 3 = 3 。
[1, 4] 和 [4] 不是 好 子集,因为乘积分别为 4 = 22 和 4 = 22 。
请你返回 nums 中不同的 好 子集的数目对 109 + 7 取余 的结果。
nums 中的 子集 是通过删除 nums 中一些(可能一个都不删除,也可能全部都删除)元素后剩余元素组成的数组。如果两个子集删除的下标不同,那么它们被视为不同的子集。
示例 1:
输入:nums = [1,2,3,4]
输出:6
解释:好子集为:
- [1,2]:乘积为 2 ,可以表示为质数 2 的乘积。
- [1,2,3]:乘积为 6 ,可以表示为互不相同的质数 2 和 3 的乘积。
- [1,3]:乘积为 3 ,可以表示为质数 3 的乘积。
- [2]:乘积为 2 ,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6 ,可以表示为互不相同的质数 2 和 3 的乘积。
- [3]:乘积为 3 ,可以表示为质数 3 的乘积。
示例 2:
输入:nums = [4,2,3,15]
输出:5
解释:好子集为:
- [2]:乘积为 2 ,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6 ,可以表示为互不相同质数 2 和 3 的乘积。
- [2,15]:乘积为 30 ,可以表示为互不相同质数 2,3 和 5 的乘积。
- [3]:乘积为 3 ,可以表示为质数 3 的乘积。
- [15]:乘积为 15 ,可以表示为互不相同质数 3 和 5 的乘积。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 30
题解
解法1:dfs
nums[i] <= 30,其中数的因子为平方数的肯定不行,即可以排除4, 8, 9,12,16,18 ,20,24,25, 27,28,我们对剩下的19个且存在的数dfs求方案即可。
带1的情况特殊,2^count[1] * res 即可((选1或者不选1)
解法2: 二进制枚举(状压)
基本跟dfs一个思路,看一下代码即可,但T了,需要优先处理出来合法状态,当我太懒了。
解法3:递推
遍历可能作为好子集元素的数,用 哈希表 d 维护能得到的乘积和对应的好子集个数。 可以递推。
const int N = 31, MOD = 1e9 + 7;
typedef long long LL;
class Solution {
public:
int count[N] = {0}, C = 1;
vector<int> path;
bool st[N] = {false}, g[N][N] = {false};
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
// u 当前枚举的数;sum 当前方案数
int dfs(int u, int sum){
if(!sum) return 0; //剪枝,0*x=0
if(u >= N){
if(path.empty()) return 0;
return sum * (LL)C % MOD;
}
// 不选u
int res = dfs(u + 1, sum);
// 选u
if(!st[u]){
bool flag = true;
for(int x : path){
if(g[x][u]){
flag = false;
break;
}
}
if(flag){
path.push_back(u);
res = (res + dfs(u + 1, sum * (LL)count[u] % MOD)) % MOD;
path.pop_back();
}
}
return res;
}
int numberOfGoodSubsets(vector<int>& nums) {
for(int x : nums) count[x] ++;
//筛掉不能选的数字, 所有包含平方因子的数标记为 true
for(int i = 2; i * i < N; i++){
for(int j = 1; j * i * i < N; j++){
st[j * i * i] = true;
}
}
//将不互质互质的数连边
for(int i = 1; i < N; i++){
for(int j = 1; j < N; j++){
if(gcd(i, j) > 1)
g[i][j] = true;
}
}
for(int i = 0; i < count[1]; i++) C = (C * 2) % MOD;
return dfs(2, 1);
}
};
const int N = 31, MOD = 1e9 + 7;
typedef long long LL;
class Solution {
public:
int count[N] = {0}, C = 1;
bool st[N] = {false}, g[N][N];
vector<int> all = {2,3,5,6,7,10,11,13,14,15,17,19,21,22,23,26,29,30};
int numberOfGoodSubsets(vector<int>& nums) {
for(int x : nums) count[x] ++;
//筛掉不能选的数字, 所有包含平方因子的数标记为 true
for(int i = 2; i * i < N; i++){
for(int j = 1; j * i * i < N; j++){
st[j * i * i] = true;
}
}
//将不互质的数连边
for(int i = 1; i < N; i++){
for(int j = 1; j < N; j++){
if(gcd(i, j) > 1)
g[i][j] = true;
}
}
LL res = 0;
int n = 18;
for(int i = 1; i < 1<<n; i++){
int t = 1;
bool flag = false;
vector<int> v;
//cout<<bitset<18>(i)<<" ";
for(int j = 0 ;j < n; j++){
if(i >> j & 1){
// 如果有一个是0,0 * x == 0
if(!count[all[j]]){
flag = true;
break;
}
// 判断前面是否出现过互质的数
for(int x : v){
if(g[x][all[j]]){
flag = true;
break;
}
}
if(flag) break;
t = (LL)t * count[all[j]] % MOD;
v.push_back(all[j]);
}
if(flag) break;
}
cout<<t<<endl;
if(!flag) res = (res + t) % MOD;
}
for(int i = 0; i < count[1]; i++) C = (C * 2) % MOD;
return res * C % MOD;
}
};
遍历可能作为好子集元素的数,用 哈希表 d 维护能得到的乘积和对应的好子集个数。 可以递推。
def numberOfGoodSubsets(self, nums: List[int]) -> int:
ct, mod = Counter(nums), 10**9+7
d = defaultdict(int)
d[1] = (1 << ct[1]) % mod
for num in [2, 3, 5, 6, 7, 10, 11, 13, 14, 15, 17, 19, 21, 22, 23, 26, 29, 30]:
for x in list(d):
if math.gcd(num, x) == 1:
d[num*x] += ct[num]*d[x]
d[num*x] %= mod
return (sum(d.values())-d[1]) % mod