Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

952. Largest Component Size by Common Factor - Leetcode Solution

Code Implementation

from collections import defaultdict

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1]*n

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px == py:
            return
        if self.size[px] < self.size[py]:
            px, py = py, px
        self.parent[py] = px
        self.size[px] += self.size[py]

class Solution:
    def largestComponentSize(self, nums):
        n = len(nums)
        uf = UnionFind(n)
        factor_map = {}
        def get_factors(x):
            factors = set()
            i = 2
            while i*i <= x:
                if x % i == 0:
                    factors.add(i)
                    while x % i == 0:
                        x //= i
                i += 1
            if x > 1:
                factors.add(x)
            return factors

        for i, num in enumerate(nums):
            for f in get_factors(num):
                if f in factor_map:
                    uf.union(i, factor_map[f])
                else:
                    factor_map[f] = i

        count = defaultdict(int)
        for i in range(n):
            root = uf.find(i)
            count[root] += 1
        return max(count.values())
      
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
using namespace std;

class UnionFind {
public:
    vector<int> parent, size;
    UnionFind(int n) : parent(n), size(n, 1) {
        for(int i = 0; i < n; ++i) parent[i] = i;
    }
    int find(int x) {
        if(parent[x] != x)
            parent[x] = find(parent[x]);
        return parent[x];
    }
    void unite(int x, int y) {
        int px = find(x), py = find(y);
        if(px == py) return;
        if(size[px] < size[py]) swap(px, py);
        parent[py] = px;
        size[px] += size[py];
    }
};

class Solution {
public:
    vector<int> get_factors(int x) {
        vector<int> res;
        for(int i = 2; i*i <= x; ++i) {
            if(x % i == 0) {
                res.push_back(i);
                while(x % i == 0) x /= i;
            }
        }
        if(x > 1) res.push_back(x);
        return res;
    }

    int largestComponentSize(vector<int>& nums) {
        int n = nums.size();
        UnionFind uf(n);
        unordered_map<int, int> factor_map;
        for(int i = 0; i < n; ++i) {
            vector<int> factors = get_factors(nums[i]);
            for(int f : factors) {
                if(factor_map.count(f))
                    uf.unite(i, factor_map[f]);
                else
                    factor_map[f] = i;
            }
        }
        unordered_map<int, int> count;
        int res = 0;
        for(int i = 0; i < n; ++i) {
            int root = uf.find(i);
            count[root]++;
            res = max(res, count[root]);
        }
        return res;
    }
};
      
import java.util.*;

class UnionFind {
    int[] parent, size;
    public UnionFind(int n) {
        parent = new int[n];
        size = new int[n];
        for(int i = 0; i < n; ++i) {
            parent[i] = i;
            size[i] = 1;
        }
    }
    public int find(int x) {
        if(parent[x] != x)
            parent[x] = find(parent[x]);
        return parent[x];
    }
    public void union(int x, int y) {
        int px = find(x), py = find(y);
        if(px == py) return;
        if(size[px] < size[py]) {
            int tmp = px; px = py; py = tmp;
        }
        parent[py] = px;
        size[px] += size[py];
    }
}

class Solution {
    private List<Integer> getFactors(int x) {
        List<Integer> res = new ArrayList<>();
        for(int i = 2; i * i <= x; ++i) {
            if(x % i == 0) {
                res.add(i);
                while(x % i == 0) x /= i;
            }
        }
        if(x > 1) res.add(x);
        return res;
    }

    public int largestComponentSize(int[] nums) {
        int n = nums.length;
        UnionFind uf = new UnionFind(n);
        Map<Integer, Integer> factorMap = new HashMap<>();
        for(int i = 0; i < n; ++i) {
            for(int f : getFactors(nums[i])) {
                if(factorMap.containsKey(f))
                    uf.union(i, factorMap.get(f));
                else
                    factorMap.put(f, i);
            }
        }
        Map<Integer, Integer> count = new HashMap<>();
        int res = 0;
        for(int i = 0; i < n; ++i) {
            int root = uf.find(i);
            count.put(root, count.getOrDefault(root, 0) + 1);
            res = Math.max(res, count.get(root));
        }
        return res;
    }
}
      
class UnionFind {
    constructor(n) {
        this.parent = Array(n).fill(0).map((_, i) => i);
        this.size = Array(n).fill(1);
    }
    find(x) {
        if(this.parent[x] !== x)
            this.parent[x] = this.find(this.parent[x]);
        return this.parent[x];
    }
    union(x, y) {
        let px = this.find(x), py = this.find(y);
        if(px === py) return;
        if(this.size[px] < this.size[py]) [px, py] = [py, px];
        this.parent[py] = px;
        this.size[px] += this.size[py];
    }
}

function getFactors(x) {
    const res = new Set();
    let i = 2;
    while(i * i <= x) {
        if(x % i === 0) {
            res.add(i);
            while(x % i === 0) x = Math.floor(x / i);
        }
        i++;
    }
    if(x > 1) res.add(x);
    return Array.from(res);
}

var largestComponentSize = function(nums) {
    const n = nums.length;
    const uf = new UnionFind(n);
    const factorMap = new Map();
    for(let i = 0; i < n; ++i) {
        for(const f of getFactors(nums[i])) {
            if(factorMap.has(f))
                uf.union(i, factorMap.get(f));
            else
                factorMap.set(f, i);
        }
    }
    const count = new Map();
    let res = 0;
    for(let i = 0; i < n; ++i) {
        const root = uf.find(i);
        count.set(root, (count.get(root) || 0) + 1);
        res = Math.max(res, count.get(root));
    }
    return res;
};
      

Problem Description

You are given an array of positive integers called nums. Two numbers are considered to be connected if they share a common factor greater than 1. A connected component is a group of numbers where every number is directly or indirectly connected to every other number in the group via these common factors.

Your task is to find the size of the largest such connected component in the array. Each number can only belong to one component, and you must not reuse elements between components.

Constraints:

  • 1 ≤ nums.length ≤ 2 * 104
  • 1 ≤ nums[i] ≤ 105
  • All elements in nums are positive integers.

Thought Process

At first glance, the problem seems to ask for the largest group of numbers that are all connected by sharing common factors. If you tried a brute-force approach, you might consider checking every pair of numbers for a common factor, but this would be far too slow for large arrays.

A more efficient way is to notice that the connection between numbers is based on their prime factors. If two numbers share any prime factor, they are connected. So, instead of comparing every pair, we can "group" numbers by their prime factors.

This leads to the idea of using a Union-Find (Disjoint Set Union) data structure. By treating each number as a node, and connecting nodes that share a factor, we can efficiently group numbers into components.

The challenge is to efficiently find and link numbers by their factors, and then determine the largest group formed.

Solution Approach

  • Step 1: Prime Factorization
    For each number in nums, compute all of its prime factors. This can be done efficiently up to 105 using trial division.
  • Step 2: Union by Factor
    We use a Union-Find structure to connect indices of numbers. For each number, for every prime factor it has, we map that factor to the index of the number. If a factor has already been seen, we union the current index with the previously seen index for that factor. This way, all numbers sharing a factor are grouped.
  • Step 3: Count Component Sizes
    After processing all numbers, we traverse through the Union-Find structure. For each number, we find its root parent and count how many numbers share the same root. The largest count is our answer.
  • Why Union-Find?
    Union-Find allows us to quickly merge sets and find their leaders, which is perfect for grouping numbers by shared factors. Using path compression and union by size/rank keeps operations nearly constant time.
  • Why Factor Map?
    The map from factor to index acts as a bridge: whenever two numbers share a factor, they are joined in the same component.

Example Walkthrough

Sample Input: nums = [4, 6, 15, 35]

  • 4 factors: 2
  • 6 factors: 2, 3
  • 15 factors: 3, 5
  • 35 factors: 5, 7

- 4 and 6 share factor 2 → connect(0,1)
- 6 and 15 share factor 3 → connect(1,2)
- 15 and 35 share factor 5 → connect(2,3)

Tracing the unions:

  • After 4 and 6: {4,6} together
  • After 6 and 15: {4,6,15} together
  • After 15 and 35: {4,6,15,35} together
All numbers are connected directly or indirectly, so the largest component is size 4.

Time and Space Complexity

  • Brute-force Approach:
    • Time: O(N^2 * logM) where N is the length of nums and M is the largest number, since checking every pair for a common factor is slow.
    • Space: O(N) for storing connections.
  • Optimized Approach (Union-Find + Factorization):
    • Time: O(N * sqrt(M)), where for each number (N), we factorize up to sqrt(M) (since we use trial division for factorization). Union-Find operations are nearly constant time.
    • Space: O(N + M), for Union-Find structures and mapping factors to indices.

Summary

The key insight is that numbers are connected through shared prime factors, not just direct pairwise comparison. By using Union-Find to group numbers by their factors, and mapping each factor to an index, we efficiently find the largest connected component. This approach avoids brute-force inefficiency and leverages mathematical properties for optimal performance.

The elegance comes from representing the problem as a graph of connections via factors, and using classic data structures to solve it efficiently.