Union Find and Prime Factorization

952. Largest Component Size by Common Factor

Union Find is a natural way to combine different sets with connection into one set. So the basic idea is:

  • If two numbers have common factor, union them
  • During the union process, maintain a set size as the final answer is asking for the set size.

Plain solution

class UF{
public:
vector<int> roots;
vector<int> sizes;
UF(int n){
roots.assign(n, 0); for (int i = 0; i < n; ++i)roots[i] = i;
sizes.assign(n, 1);
}
int find_set(int x){
return x == roots[x]? x: roots[x] = find_set(roots[x]);
}
void union_set(int x, int y){
int root_x = find_set(x), root_y = find_set(y);
if (root_x == root_y)return;
sizes[root_x] += sizes[root_y];
roots[root_y] = root_x;
}
int get_set_size(int x){
return sizes[x];
}
};
class Solution {
public:
int largestComponentSize(vector<int>& nums) {
const int n = nums.size();
UF uf(n);
unordered_map<int, vector<int>> graph;
for (int i = 0; i < n; ++i){
int num = nums[i];
for (int factor = 1; factor <= int(sqrt(num)); ++factor){
if (num % factor == 0){
if(factor != 1)graph[factor].push_back(i);
if(num / factor != 1)graph[num / factor].push_back(i);
}
}
}
for (auto [k, v]: graph){
const int m = v.size();
for (int i = 1; i < m ; ++i){
uf.union_set(v[0], v[i]);
}
}
int ret = 0;
for (int i = 0; i < n; ++i){
ret = max(ret, uf.get_set_size(i));
}
return ret;
}
};

Speed up by using Prime Factorization

For example, for the number 8 and 12, the have the common factor of

{2, 4}, actually if they have the common prime factor will be enough as non prime factor can be further split to prime factors. In this example, 4 can be split to 2 * 2. So instead of using all factors, we can only use the prime factor to build the connection. For prime factorization, I borrowed it from [1].

class UF{
public:
vector<int> roots;
vector<int> sizes;
UF(int n){
roots.assign(n, 0); for (int i = 0; i < n; ++i)roots[i] = i;
sizes.assign(n, 1);

}
int find_set(int x){
return x == roots[x]? x: roots[x] = find_set(roots[x]);
}
void union_set(int x, int y){
int root_x = find_set(x), root_y = find_set(y);
if (root_x == root_y)return;
sizes[root_x] += sizes[root_y];
roots[root_y] = root_x;
}
int get_set_size(int x){
return sizes[x];
}
};
vector<int> primeFactors(int n)
{

vector<int>ret;
// Print the number of 2s that divide n
while (n % 2 == 0)
{
ret.push_back(2);
n = n/2;
}

// n must be odd at this point. So we can skip
// one element (Note i = i +2)
for (int i = 3; i <= sqrt(n); i = i + 2)
{
// While i divides n, print i and divide n
while (n % i == 0)
{
ret.push_back(i);
n = n/i;
}
}

// This condition is to handle the case when n
// is a prime number greater than 2
if (n > 2)
ret.push_back(n);
return ret;
}
class Solution {
public:
int largestComponentSize(vector<int>& nums) {
const int n = nums.size();
UF uf(n);
unordered_map<int, vector<int>> graph;
for (int i = 0; i < n; ++i){
int num = nums[i];
auto factors = primeFactors(num);
for (auto &factor: factors)graph[factor].push_back(i);
}
for (auto [k, v]: graph){
const int m = v.size();
for (int i = 1; i < m ; ++i){
uf.union_set(v[0], v[i]);
}
}
int ret = 0;
for (int i = 0; i < n; ++i){
ret = max(ret, uf.get_set_size(i));
}
return ret;
}
};

Run time

Time difference Top submission is the plain version and the bottom one is using the Prime Factorization version

Reference

[1] https://www.geeksforgeeks.org/print-all-prime-factors-of-a-given-number/

Data Scientist/MLE/SWE @takemobi