给你一个正整数数组 nums
。
如果数组 nums
的子集中的元素乘积是一个 无平方因子数 ,则认为该子集是一个 无平方 子集。
无平方因子数 是无法被除 1
之外任何平方数整除的数字。
返回数组 nums
中 无平方 且 非空 的子集数目。因为答案可能很大,返回对 109 + 7
取余的结果。
nums
的 非空子集 是可以由删除 nums
中一些元素(可以不删除,但不能全部删除)得到的一个数组。如果构成两个子集时选择删除的下标不同,则认为这两个子集不同。
示例 1:
**输入:** nums = [3,4,4,5]
**输出:** 3
**解释:** 示例中有 3 个无平方子集:
- 由第 0 个元素 [3] 组成的子集。其元素的乘积是 3 ,这是一个无平方因子数。
- 由第 3 个元素 [5] 组成的子集。其元素的乘积是 5 ,这是一个无平方因子数。
- 由第 0 个和第 3 个元素 [3,5] 组成的子集。其元素的乘积是 15 ,这是一个无平方因子数。
可以证明给定数组中不存在超过 3 个无平方子集。
示例 2:
**输入:** nums = [1]
**输出:** 1
**解释:** 示例中有 1 个无平方子集:
- 由第 0 个元素 [1] 组成的子集。其元素的乘积是 1 ,这是一个无平方因子数。
可以证明给定数组中不存在超过 1 个无平方子集。
提示:
1 <= nums.length <= 1000
1 <= nums[i] <= 30
方法一:转换成 0-1 背包方案数 把无平方因子数的数字记作 SF(square-free number)。
对于每个 [2,30] 内的 SF,通过预处理得到每个 SF 的质因子集合,用二进制表示。二进制从低到高第 i 个比特为 1 表示第 i 个质数在集合中,为 0 表示第 i 个质数不在集合中。
那么把每个是 SF 的 nums}[i] 转换成对应的质因子集合,题目就变成「遍历所有由 30 以内的质数组成的集合 j(这有 2^{10 个),对每个 j,计算选一些不相交 的质因子集合,它们的并 集恰好 为 j 的方案数」。
这是 0-1 背包求方案数的模型,具体可以看【基础算法精讲 18】 。
附:本题视频讲解 。
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PRIMES = 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 SF_TO_MASK = [0 ] * 31 for i in range (2 , 31 ): for j, p in enumerate (PRIMES): if i % p == 0 : if i % (p * p) == 0 : SF_TO_MASK[i] = -1 break SF_TO_MASK[i] |= 1 << j class Solution : def squareFreeSubsets (self, nums: List [int ] ) -> int : MOD = 10 ** 9 + 7 M = 1 << len (PRIMES) f = [0 ] * M f[0 ] = 1 for x in nums: mask = SF_TO_MASK[x] if mask >= 0 : for j in range (M - 1 , mask - 1 , -1 ): if (j | mask) == j: f[j] = (f[j] + f[j ^ mask]) % MOD return (sum (f) - 1 ) % MOD
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { private static final int [] PRIMES = new int []{2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 }; private static final int MOD = (int ) 1e9 + 7 , MX = 30 , N_PRIMES = PRIMES.length, M = 1 << N_PRIMES; private static final int [] SF_TO_MASK = new int [MX + 1 ]; static { for (int i = 2 ; i <= MX; ++i) for (int j = 0 ; j < N_PRIMES; ++j) { int p = PRIMES[j]; if (i % p == 0 ) { if (i % (p * p) == 0 ) { SF_TO_MASK[i] = -1 ; break ; } SF_TO_MASK[i] |= 1 << j; } } } public int squareFreeSubsets (int [] nums) { var f = new int [M]; f[0 ] = 1 ; for (int x : nums) { int mask = SF_TO_MASK[x]; if (mask >= 0 ) for (int j = M - 1 ; j >= mask; --j) if ((j | mask) == j) f[j] = (f[j] + f[j ^ mask]) % MOD; } var ans = 0L ; for (int v : f) ans += v; return (int ) ((ans - 1 ) % MOD); } }
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { static constexpr int PRIMES[] = {2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 }; static constexpr int MOD = 1e9 + 7 , MX = 30 , N_PRIMES = 10 , M = 1 << N_PRIMES; public : int squareFreeSubsets (vector<int > &nums) { int sf2mask[MX + 1 ]{}; for (int i = 2 ; i <= MX; ++i) for (int j = 0 ; j < N_PRIMES; ++j) { int p = PRIMES[j]; if (i % p == 0 ) { if (i % (p * p) == 0 ) { sf2mask[i] = -1 ; break ; } sf2mask[i] |= 1 << j; } } int f[M]{1 }; for (int x : nums) if (int mask = sf2mask[x]; mask >= 0 ) for (int j = M - 1 ; j >= mask; --j) if ((j | mask) == j) f[j] = (f[j] + f[j ^ mask]) % MOD; return (accumulate (f, f + M, 0L ) - 1 ) % MOD; } };
[sol1-Go] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 var primes = [...]int {2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 }var sf2mask = [31 ]int {} func init () { for i := 2 ; i <= 30 ; i++ { for j, p := range primes { if i%p == 0 { if i%(p*p) == 0 { sf2mask[i] = -1 break } sf2mask[i] |= 1 << j } } } } func squareFreeSubsets (nums []int ) int { const mod int = 1e9 + 7 const m = 1 << len (primes) f := [m]int {1 } for _, x := range nums { if mask := sf2mask[x]; mask >= 0 { for j := m - 1 ; j >= mask; j-- { if j|mask == j { f[j] = (f[j] + f[j^mask]) % mod } } } } ans := 0 for _, v := range f { ans += v } return (ans - 1 ) % mod }
复杂度分析
时间复杂度:O(n2^m),其中 n 为 nums 的长度,m=\pi(\max(\textit{nums})),\pi(N) 表示 N 以内的质数个数,对于本题的数据范围,m\le 10。
空间复杂度:O(2^m)。
方法二:子集状压 DP 如果把 nums 的长度增加到 10^5 的话,方法一就太慢了。
毕竟 nums 的值域很小,把相同数字一并处理 是更优的。
怎么转移呢?选择的数字乘积不能有平方因子,对应的质数集合也就不能有交集。那么枚举 mask 的补集 other 的子集 j,这样可以保证 mask 和 j 是没有交集的。站在并集 j\cup \textit{mask 的角度,按照「选或不选」的思想,如果选了 mask,那么就要从 f[j] 转移过来了。
具体地,设 x 是 mask 对应的 SF,cnt}[x] 为 x 在 nums 中的出现次数,那么需要从 cnt}[x] 个 mask 中选一个 ,与 j 并成 j\cup \textit{mask,对应的转移为
f[j\cup \textit{mask}] += f[j] \cdot \textit{cnt}[x]
附:视频讲解
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PRIMES = 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 SF_TO_MASK = [0 ] * 31 for i in range (2 , 31 ): for j, p in enumerate (PRIMES): if i % p == 0 : if i % (p * p) == 0 : SF_TO_MASK[i] = -1 break SF_TO_MASK[i] |= 1 << j class Solution : def squareFreeSubsets (self, nums: List [int ] ) -> int : MOD = 10 ** 9 + 7 cnt = Counter(nums) M = 1 << len (PRIMES) f = [0 ] * M f[0 ] = pow (2 , cnt[1 ], MOD) for x, c in cnt.items(): mask = SF_TO_MASK[x] if mask > 0 : j = other = (M - 1 ) ^ mask while True : f[j | mask] = (f[j | mask] + f[j] * c) % MOD j = (j - 1 ) & other if j == other: break return (sum (f) - 1 ) % MOD
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution { private static final int [] PRIMES = new int []{2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 }; private static final int MOD = (int ) 1e9 + 7 , MX = 30 , N_PRIMES = PRIMES.length, M = 1 << N_PRIMES; private static final int [] SF_TO_MASK = new int [MX + 1 ]; static { for (int i = 2 ; i <= MX; ++i) for (int j = 0 ; j < N_PRIMES; ++j) { int p = PRIMES[j]; if (i % p == 0 ) { if (i % (p * p) == 0 ) { SF_TO_MASK[i] = -1 ; break ; } SF_TO_MASK[i] |= 1 << j; } } } public int squareFreeSubsets (int [] nums) { var cnt = new int [MX + 1 ]; int pow2 = 1 ; for (int x : nums) if (x == 1 ) pow2 = pow2 * 2 % MOD; else ++cnt[x]; var f = new long [M]; f[0 ] = pow2; for (int x = 2 ; x <= MX; ++x) { int mask = SF_TO_MASK[x], c = cnt[x]; if (mask > 0 && c > 0 ) { int other = (M - 1 ) ^ mask, j = other; do { f[j | mask] = (f[j | mask] + f[j] * cnt[x]) % MOD; j = (j - 1 ) & other; } while (j != other); } } var ans = -1L ; for (var v : f) ans += v; return (int ) (ans % MOD); } }
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { static constexpr int PRIMES[] = {2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 }; static constexpr int MOD = 1e9 + 7 , MX = 30 , N_PRIMES = 10 , M = 1 << N_PRIMES; public : int squareFreeSubsets (vector<int > &nums) { int sf2mask[MX + 1 ]{}; for (int i = 2 ; i <= MX; ++i) for (int j = 0 ; j < N_PRIMES; ++j) { int p = PRIMES[j]; if (i % p == 0 ) { if (i % (p * p) == 0 ) { sf2mask[i] = 0 ; break ; } sf2mask[i] |= 1 << j; } } int cnt[MX + 1 ]{}, pow2 = 1 ; for (int x : nums) if (x == 1 ) pow2 = pow2 * 2 % MOD; else ++cnt[x]; long f[M]{pow2}; for (int x = 2 ; x <= MX; ++x) { int mask = sf2mask[x], c = cnt[x]; if (mask && c) { int other = (M - 1 ) ^ mask, j = other; do { f[j | mask] = (f[j | mask] + f[j] * cnt[x]) % MOD; j = (j - 1 ) & other; } while (j != other); } } return accumulate (f, f + M, -1L ) % MOD; } };
[sol2-Go] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 var primes = [...]int {2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 }var sf2mask = [31 ]int {} func init () { for i := 2 ; i <= 30 ; i++ { for j, p := range primes { if i%p == 0 { if i%(p*p) == 0 { sf2mask[i] = -1 break } sf2mask[i] |= 1 << j } } } } func squareFreeSubsets (a []int ) int { const mod int = 1e9 + 7 cnt, pow2 := [31 ]int {}, 1 for _, x := range a { if x == 1 { pow2 = pow2 * 2 % mod } else { cnt[x]++ } } const m = 1 << len (primes) f := [m]int {pow2} for sf, mask := range sf2mask { if mask > 0 && cnt[sf] > 0 { other := (m - 1 ) ^ mask for j := other; ; { f[j|mask] = (f[j|mask] + f[j]*cnt[sf]) % mod j = (j - 1 ) & other if j == other { break } } } } ans := -1 for _, v := range f { ans += v } return ans % mod }
复杂度分析
时间复杂度:O(n+q2^m),其中 n 为 nums 的长度,q 为 \max(\textit{nums}) 以内的 SF 个数,m=\pi(\max(\textit{nums})),\pi(N) 表示 N 以内的质数个数,对于本题的数据范围,q\le 18, m\le 10。
空间复杂度:O(2^m)。