Count the Number of Square-Free Subsets — Hard Problem & Solution
A number is square-free when no perfect square above 1 divides it. A subset of nums is square-free when the product of its elements is square-free.
- Difficulty: Hard
- Topics: Math, Dynamic Programming, Bit Manipulation, Bitmask
- Asked at: Amazon, Google, Uber
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
A number is square-free when no perfect square above 1 divides it. A subset of nums is square-free when the product of its elements is square-free.
Return the number of non-empty square-free subsets, modulo 10^9 + 7. Subsets are distinguished by the indices they use.
Example 1
Input: nums = [3,4,4,5]
Output: 3
Explanation: The square-free subsets are {3}, {5} and {3,5} — 4 is divisible by 4.
Example 2
Input: nums = [1]
Output: 1
Explanation: 1 is square-free.
Example 3
Input: nums = [2,3,6]
Output: 4
Explanation: {2}, {3}, {6} and {2,3}; pairing 6 with either factor repeats a prime.
Constraints
1 <= nums.length <= 10001 <= nums[i] <= 30
How to solve Count the Number of Square-Free Subsets
A product is square-free precisely when no prime repeats, so each usable value becomes a set of primes and a valid subset is a collection of pairwise-disjoint sets. Counting those is a DP over the 1024 possible prime sets.
Approach
- For each value, build its prime mask, marking it unusable if any prime appears squared.
- Keep
dp[mask], the number of subsets whose combined prime set is exactlymask, seeded withdp[0] = 1for the empty subset. - For each usable value, iterate the states downward and add
dp[s]intodp[s | m]wheneversandmare disjoint. - Sum all states and subtract 1 for the empty subset.
Why it works
Iterating the states in decreasing order is what stops a value from being used twice within one pass. The value 1 has an empty mask, so dp[s] += dp[s] doubles every count — exactly right, since each 1 may independently be included or left out.
Complexity
- Time —
O(n · 1024) - Space —
O(1024)
Pitfalls
- Forgetting to subtract the empty subset overcounts by one.
- Treating
1as unusable loses the factor of2^(number of ones). - Iterating states upward lets a single element be chosen more than once.
Reference solution
Python
from typing import List
def squareFreeSubsets(nums: List[int]) -> int:
MOD = 1000000007
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
def mask_of(v: int) -> int:
m = 0
for i, p in enumerate(primes):
if v % p == 0:
if v % (p * p) == 0:
return -1
m |= 1 << i
return m
dp = [0] * 1024
dp[0] = 1
for v in nums:
m = mask_of(v)
if m < 0:
continue
for s in range(1023, -1, -1):
if dp[s] and not (s & m):
dp[s | m] = (dp[s | m] + dp[s]) % MOD
return (sum(dp) - 1) % MODJavaScript
var squareFreeSubsets = function(nums) {
var MOD = 1000000007;
var primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
var maskOf = function(v) {
var m = 0;
for (var i = 0; i < primes.length; i++) {
var p = primes[i];
if (v % p === 0) {
if (v % (p * p) === 0) return -1;
m |= 1 << i;
}
}
return m;
};
var dp = [];
for (var t = 0; t < 1024; t++) dp.push(0);
dp[0] = 1;
for (var j = 0; j < nums.length; j++) {
var m = maskOf(nums[j]);
if (m < 0) continue;
for (var s = 1023; s >= 0; s--) {
if (dp[s] === 0) continue;
if ((s & m) !== 0) continue;
dp[s | m] = (dp[s | m] + dp[s]) % MOD;
}
}
var total = 0;
for (var u = 0; u < 1024; u++) total = (total + dp[u]) % MOD;
return (total - 1 + MOD) % MOD;
};Also on the editorial tab: C, C#, C++, Go, Java, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.