Largest Component Size by Common Factor
HardPrompt
You are given an integer array of unique positive integers nums. Consider the following graph:
- There are
nums.lengthnodes, labelednums[0]tonums[nums.length - 1], - There is an undirected edge between
nums[i]andnums[j]ifnums[i]andnums[j]share a common factor greater than1.
Return the size of the largest connected component in the graph.
Example 1:
Input: nums = [4,6,15,35]
Output: 4Example 2:
Input: nums = [20,50,9,63]
Output: 2Example 3:
Input: nums = [2,3,6,7,4,12,21,39]
Output: 8
Constraints:
1 <= nums.length <= 2 * 1041 <= nums[i] <= 105- All the values of
numsare unique.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly translates the problem statement into a graph data structure. It builds an explicit graph where nodes are the numbers from the input array. An edge is added between two nodes if their corresponding numbers share a common factor greater than 1. After building the graph, a standard graph traversal algorithm like Depth-First Search (DFS) or Breadth-First Search (BFS) is used to find the sizes of all connected components.
Algorithm
- Initialize an adjacency list to represent the graph. The nodes correspond to the indices of the
numsarray. - Iterate through every unique pair of numbers
(nums[i], nums[j])from the input array. - For each pair, calculate their Greatest Common Divisor (GCD) using the Euclidean algorithm.
- If
gcd(nums[i], nums[j]) > 1, add an edge between nodeiand nodejin the adjacency list. - Initialize a
visitedarray to keep track of visited nodes and a variablemaxSizeto store the size of the largest component found so far. - Iterate from
i = 0tonums.length - 1. If nodeihas not been visited:- Start a DFS or BFS traversal from node
i. - Count the number of nodes in the component discovered by the traversal.
- Update
maxSizewith the maximum of its current value and the size of the new component.
- Start a DFS or BFS traversal from node
- Return
maxSize.
Walkthrough
class Solution { public int largestComponentSize(int[] nums) { int n = nums.length; List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (gcd(nums[i], nums[j]) > 1) { adj.get(i).add(j); adj.get(j).add(i); } } } boolean[] visited = new boolean[n]; int maxSize = 0; for (int i = 0; i < n; i++) { if (!visited[i]) { int currentSize = dfs(i, adj, visited); maxSize = Math.max(maxSize, currentSize); } } return maxSize; } private int dfs(int u, List<List<Integer>> adj, boolean[] visited) { visited[u] = true; int count = 1; for (int v : adj.get(u)) { if (!visited[v]) { count += dfs(v, adj, visited); } } return count; } private int gcd(int a, int b) { while (b != 0) { int temp = b; b = a % b; a = temp; } return a; }}Complexity
Time
O(N^2 * log(M)), where N is the length of `nums` and M is the maximum value in `nums`. Building the graph requires checking `O(N^2)` pairs, and each check involves a GCD calculation which takes `O(log(M))` time. The subsequent graph traversal is dominated by the graph construction time.
Space
O(N^2), where N is the number of elements in `nums`. This is for storing the adjacency list, which can have up to `O(N^2)` edges in a dense graph.
Trade-offs
Pros
Simple to understand and directly follows the problem definition.
Cons
Inefficient for the given constraints.
N^2operations are too slow.The space complexity can be very high, up to
O(N^2), if the graph is dense.This approach will likely result in a "Time Limit Exceeded" or "Memory Limit Exceeded" error on competitive programming platforms.
This approach improves upon the brute-force method by avoiding the explicit construction of the graph. It uses a Disjoint Set Union (DSU) or Union-Find data structure to group numbers into connected components. The key insight is that two numbers are in the same component if they share a common prime factor. We can iterate through each number, find its prime factors, and union it with any other number that shares one of those primes.
Algorithm
- Initialize a Union-Find data structure for
Nelements, whereNis the length ofnums. Each numbernums[i]initially belongs to its own set. - Create a hash map
primeToIndexto store the first indexiencountered for each prime factorp. - Iterate through each number
x = nums[i]fromi = 0toN-1. - For the current number
x, find all its distinct prime factors. A simple way is to use trial division up tosqrt(x). - For each prime factor
pofx:- Check if
pexists as a key inprimeToIndex. - If it does, it means we've seen this prime before in another number
nums[j], wherej = primeToIndex.get(p). We then union the sets ofiandj. - If
pdoes not exist in the map, we are seeing this prime for the first time. Store the current indexiin the map:primeToIndex.put(p, i).
- Check if
- After processing all numbers, the DSU structure contains the connected components.
- Iterate through all numbers again, count the size of each component (set) using a hash map, and find the maximum size.
Walkthrough
class Solution { public int largestComponentSize(int[] nums) { int n = nums.length; DSU dsu = new DSU(n); Map<Integer, Integer> primeToIndex = new HashMap<>(); for (int i = 0; i < n; i++) { int num = nums[i]; for (int p = 2; p * p <= num; p++) { if (num % p == 0) { if (!primeToIndex.containsKey(p)) { primeToIndex.put(p, i); } dsu.union(i, primeToIndex.get(p)); while (num % p == 0) { num /= p; } } } if (num > 1) { // Remaining factor is prime if (!primeToIndex.containsKey(num)) { primeToIndex.put(num, i); } dsu.union(i, primeToIndex.get(num)); } } Map<Integer, Integer> componentCounts = new HashMap<>(); int maxSize = 0; for (int i = 0; i < n; i++) { int root = dsu.find(i); int count = componentCounts.getOrDefault(root, 0) + 1; componentCounts.put(root, count); maxSize = Math.max(maxSize, count); } return maxSize; }} class DSU { int[] parent; public DSU(int n) { parent = new int[n]; for (int i = 0; i < n; i++) parent[i] = i; } public int find(int i) { if (parent[i] == i) return i; return parent[i] = find(parent[i]); } public void union(int i, int j) { int rootI = find(i); int rootJ = find(j); if (rootI != rootJ) parent[rootJ] = rootI; }}Complexity
Time
O(N * sqrt(M)), where N is `nums.length` and M is the max value in `nums`. The bottleneck is the prime factorization for each of the N numbers, which takes `O(sqrt(M))` time. Union-Find operations are nearly constant time.
Space
O(N + P), where N is `nums.length` and P is the number of unique prime factors across all numbers. P is bounded by `π(M)`, the number of primes up to the maximum value M. This is efficient.
Trade-offs
Pros
Much more time and space efficient than the brute-force approach.
Avoids building a large
O(N^2)graph by using an efficient DSU data structure.
Cons
The prime factorization step, which takes
O(sqrt(M))for each number, can still be a bottleneck ifNandMare large.While much better than brute-force, it may be borderline on passing the time limits for the strictest constraints.
This is the most efficient approach. It builds upon the Union-Find idea but optimizes the prime factorization step. Instead of factoring each number individually, we first pre-compute the smallest prime factor (SPF) for all numbers up to the maximum value in nums using a sieve. This allows for very fast factorization. The DSU structure then connects numbers with their prime factors, efficiently forming the components.
Algorithm
- Find the maximum number
Min thenumsarray. - Pre-computation: Create an array
spf(Smallest Prime Factor) of sizeM+1. Populate it using a sieve algorithm inO(M log log M)time.spf[i]will store the smallest prime that dividesi. - Union-Find Model: Initialize a DSU structure for
M+1elements. This DSU will manage sets for all numbers from0toM, allowing us to directly union a numberxwith its prime factorsp. - Iterate through each number
xin thenumsarray. - For each
x, find its distinct prime factors using the pre-computedspfarray. This is done by repeatedly dividingxbyspf[x]untilxbecomes 1. This process takesO(log x)time. - For each prime factor
pfound, perform a union operation between the original numberxand the primep:dsu.union(x, p). - Counting: After processing all numbers, create a map to count the number of
numselements belonging to each component's root. - Iterate through
numsone last time. For eachx, find its rootr = dsu.find(x)and increment the count forr. - The answer is the maximum count found in the map.
Walkthrough
class Solution { public int largestComponentSize(int[] nums) { int maxVal = 0; for (int num : nums) { maxVal = Math.max(maxVal, num); } DSU dsu = new DSU(maxVal + 1); int[] spf = sieve(maxVal); for (int num : nums) { int temp = num; while (temp > 1) { int p = spf[temp]; dsu.union(num, p); while (temp % p == 0) { temp /= p; } } } Map<Integer, Integer> componentCounts = new HashMap<>(); int maxSize = 0; for (int num : nums) { int root = dsu.find(num); int count = componentCounts.getOrDefault(root, 0) + 1; componentCounts.put(root, count); maxSize = Math.max(maxSize, count); } return maxSize; } private int[] sieve(int n) { int[] spf = new int[n + 1]; for (int i = 2; i <= n; i++) { spf[i] = i; } for (int i = 2; i * i <= n; i++) { if (spf[i] == i) { // i is prime for (int j = i * i; j <= n; j += i) { if (spf[j] == j) { // if spf[j] is not set yet spf[j] = i; } } } } return spf; }} class DSU { int[] parent; public DSU(int n) { parent = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; } } public int find(int i) { if (parent[i] == i) { return i; } return parent[i] = find(parent[i]); } public void union(int i, int j) { int rootI = find(i); int rootJ = find(j); if (rootI != rootJ) { parent[rootJ] = rootI; } }}Complexity
Time
O(M log log M + N * log(M) * α(M)), where N is `nums.length`, M is the max value, and α is the inverse Ackermann function. `O(M log log M)` is for the sieve, and `O(N * log M)` is for iterating through numbers and their factors. This is highly efficient.
Space
O(M), where M is the maximum value in `nums`. This space is used for the `spf` array and the DSU `parent` array.
Trade-offs
Pros
Optimal time complexity for the given constraints.
The sieve pre-computation significantly speeds up the critical step of finding prime factors, making the overall solution very fast.
Cons
More complex to implement due to the sieve pre-computation step.
Uses more memory,
O(M), which could be large if the maximum number is much larger thanN.
Solutions
Solution
class UnionFind { int [] p ; UnionFind ( int n ) { p = new int [ n ]; for ( int i = 0 ; i < n ; ++ i ) { p [ i ] = i ; } } void union ( int a , int b ) { int pa = find ( a ), pb = find ( b ); if ( pa != pb ) { p [ pa ] = pb ; } } int find ( int x ) { if ( p [ x ] != x ) { p [ x ] = find ( p [ x ]); } return p [ x ]; } } class Solution { public int largestComponentSize ( int [] nums ) { int m = 0 ; for ( int v : nums ) { m = Math . max ( m , v ); } UnionFind uf = new UnionFind ( m + 1 ); for ( int v : nums ) { int i = 2 ; while ( i <= v / i ) { if ( v % i == 0 ) { uf . union ( v , i ); uf . union ( v , v / i ); } ++ i ; } } int [] cnt = new int [ m + 1 ]; int ans = 0 ; for ( int v : nums ) { int t = uf . find ( v ); ++ cnt [ t ]; ans = Math . max ( ans , cnt [ t ]); } return ans ; } }class UnionFind { public: vector < int > p ; int n ; UnionFind ( int _n ) : n ( _n ) , p ( _n ) { iota ( p . begin (), p . end (), 0 ); } void unite ( int a , int b ) { int pa = find ( a ), pb = find ( b ); if ( pa != pb ) p [ pa ] = pb ; } int find ( int x ) { if ( p [ x ] != x ) p [ x ] = find ( p [ x ]); return p [ x ]; } }; class Solution { public: int largestComponentSize ( vector < int >& nums ) { int m = * max_element ( nums . begin (), nums . end ()); UnionFind * uf = new UnionFind ( m + 1 ); for ( int v : nums ) { int i = 2 ; while ( i <= v / i ) { if ( v % i == 0 ) { uf -> unite ( v , i ); uf -> unite ( v , v / i ); } ++ i ; } } vector < int > cnt ( m + 1 ); int ans = 0 ; for ( int v : nums ) { int t = uf -> find ( v ); ++ cnt [ t ]; ans = max ( ans , cnt [ t ]); } return ans ; } };class UnionFind : def __init__ ( self , n ): self . p = list ( range ( n )) def union ( self , a , b ): pa , pb = self . find ( a ), self . find ( b ) if pa != pb : self . p [ pa ] = pb def find ( self , x ): if self . p [ x ] != x : self . p [ x ] = self . find ( self . p [ x ]) return self . p [ x ] class Solution : def largestComponentSize ( self , nums : List [ int ]) -> int : uf = UnionFind ( max ( nums ) + 1 ) for v in nums : i = 2 while i <= v // i : if v % i == 0 : uf . union ( v , i ) uf . union ( v , v // i ) i += 1 return max ( Counter ( uf . find ( v ) for v in nums ). values ())Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.