难度困难
给定一个由不同正整数的组成的非空数组 nums
,考虑下面的图:
- 有
nums.length
个节点,按从nums[0]
到nums[nums.length - 1]
标记; - 只有当
nums[i]
和nums[j]
共用一个大于 1 的公因数时,nums[i]
和nums[j]
之间才有一条边。
返回 图中最大连通组件的大小 。
示例 1:
输入:nums = [4,6,15,35]
输出:4
示例 2:
输入:nums = [20,50,9,63]
输出:2
示例 3:
输入:nums = [2,3,6,7,4,12,21,39]
输出:8
提示:
1 <= nums.length <= 2 * 10^4
1 <= nums[i] <= 10^5
nums
中所有值都 不同
# 分解质因数 + 并查集
每个数看作一个点,那么只有二者的 时,才会有边。
若直接每两个元素建图,那么时间复杂度为 级别。
我们可以用质因数作为中间点,让元素与其所有的质因数构成边。最后检查连通分量的最大值即可
由于每个数最多有 个质因数,即最终的时间复杂度为 ,其中 为并查集的操作复杂度
class Solution { | |
public int largestComponentSize(int[] nums) { | |
// 分解质因数 + 并查集 | |
// 以质因数作为转折点 | |
init(200002); | |
int n = nums.length; | |
for (int j = 0; j < n; j++) { | |
int num = nums[j]; | |
// 分解质因数 | |
for (int i = 2; i * i <= num; i++) { | |
boolean f = false; | |
while (num % i == 0) { | |
f = true; | |
num /= i; | |
} | |
// 合并 j, i | |
if (f) { | |
union(j, n + i); | |
} | |
} | |
// 若 num > 1,那么此时 num 也是一个质因数 | |
if (num > 1) { | |
union(j, n + num); | |
} | |
} | |
// 检查连通分量 | |
int ans = 0; | |
int[] cnt = new int[n]; | |
for (int i = 0; i < n; i++) { | |
ans = Math.max(ans, ++cnt[find(i)]); | |
} | |
return ans; | |
} | |
int[] father; | |
public void init(int n) { | |
father = new int[n]; | |
for (int i = 1; i < n; i++) { | |
father[i] = i; | |
} | |
} | |
public void union(int i, int j) { | |
int lAncestor = find(i); | |
int rAncestor = find(j); | |
// 让质因数的父节点指向原数 | |
// 这样在分组的时候使得父节点不会超过 n | |
// - cnt[find(i)]++; | |
father[rAncestor] = lAncestor; | |
} | |
public int find(int x) { | |
if (x == father[x]) { | |
return x; | |
} | |
return father[x] = find(father[x]); | |
} | |
} |