{x}
blog image

Largest Component Size by Common Factor

You are given an integer array of unique positive integers nums. Consider the following graph:

  • There are nums.length nodes, labeled nums[0] to nums[nums.length - 1],
  • There is an undirected edge between nums[i] and nums[j] if nums[i] and nums[j] share a common factor greater than 1.

Return the size of the largest connected component in the graph.

 

Example 1:

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

Example 2:

Input: nums = [20,50,9,63]
Output: 2

Example 3:

Input: nums = [2,3,6,7,4,12,21,39]
Output: 8

 

Constraints:

  • 1 <= nums.length <= 2 * 104
  • 1 <= nums[i] <= 105
  • All the values of nums are unique.

Solution Explanation:

This problem asks to find the size of the largest connected component in a graph where nodes are numbers from the input array nums, and an edge exists between two nodes if they share a common factor greater than 1. The most efficient way to solve this is using Union-Find.

Algorithm:

  1. Union-Find Data Structure: We employ a Union-Find data structure to efficiently track connected components. Each node (number) initially represents its own component. We'll use the find operation to determine the root (representative) of a component and the union operation to merge components.

  2. Finding Common Factors: For each number v in nums, we iterate through its possible factors from 2 up to the square root of v. If i is a factor, then v/i is also a factor. We perform a union operation between v, i, and v/i in our Union-Find structure. This connects nodes sharing common factors.

  3. Counting Component Sizes: After processing all numbers, we iterate through nums again, using the find operation to determine the root of each node's component. We count the occurrences of each root, representing the size of each connected component. The largest count represents the size of the largest connected component.

Time Complexity:

  • Finding factors for each number takes O(√v) time, where v is the maximum number in nums.
  • The Union-Find operations (find and union) have amortized O(α(n)) time complexity, where α(n) is the inverse Ackermann function, which grows extremely slowly and is considered practically constant for all practical input sizes.
  • Iterating through nums twice is O(n).

Therefore, the overall time complexity is dominated by finding factors, resulting in O(n√v), where n is the length of nums and v is the maximum value in nums.

Space Complexity:

The space complexity is determined by the Union-Find data structure, which needs to store the parent pointers for each node (up to the maximum number in nums). Additionally, we need space to store the counts of component sizes. Thus the space complexity is O(v).

Code Explanations (with detailed comments):

Python:

class UnionFind:
    def __init__(self, n):  # Constructor for Union-Find
        self.p = list(range(n)) # Initialize parent array: each node is its own parent
 
    def union(self, a, b):  # Union operation
        pa, pb = self.find(a), self.find(b)  # Find roots of a and b
        if pa != pb:  # If they are in different components
            self.p[pa] = pb  # Merge components by setting one root as the parent of the other
 
    def find(self, x):  # Find operation (path compression)
        if self.p[x] != x:  # If x is not its own root
            self.p[x] = self.find(self.p[x])  # Recursively find root and update parent
        return self.p[x] #Return root
 
 
class Solution:
    def largestComponentSize(self, nums: List[int]) -> int:
        uf = UnionFind(max(nums) + 1) # Initialize Union-Find with max number + 1
        for v in nums:  # Iterate through each number in nums
            i = 2  # Start checking for factors from 2
            while i * i <= v: # Check up to square root of v, optimization
                if v % i == 0:  # If i is a factor
                    uf.union(v, i)  # Union v and i
                    uf.union(v, v // i)  # Union v and v//i
                i += 1
        return max(Counter(uf.find(v) for v in nums).values()) # count occurrences of roots and return max

The other languages (Java, C++, Go) follow a very similar structure and logic, primarily differing in syntax and data structure implementations. The core algorithm remains the same.