You are given an integer array of unique positive integers nums
. Consider the following graph:
nums.length
nodes, labeled nums[0]
to nums[nums.length - 1]
,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
nums
are unique.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:
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.
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.
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:
nums
.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.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).
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.