题目描述
给定一个由不同正整数组成的非空数组nums,求每个数的公因数,如果nums[i]和nums[j]存在公因数,则说明这2个数有一条边;
请返回最大的连通性大小,最大的连通性就是能有一条边连接到一起的节点总数。
示例1:
输入:nums = [4, 6, 15, 25, 11, 77]
输出:4
解释:[4, 6, 15, 25]的连通性是4,[11, 77]的连通性是2
解题思路
这道题实际考察的是图的连通性问题;
可以拆解成3个题目,第一个求公因数,第二个使用并查集求最大连通性,第三个对并查集的深度做优化。
求公因数
从2到nums[i]的平方根,逐个计算,计算这个数的公因数。 代码如下所示:
for (int num : nums) {
int sqrt = (int) Math.sqrt(num);
for (int i = 2; i <= sqrt; i++) {
if (num % i == 0) {
int v1 = i;
int v2 = num / i;
}
}
}
并查集
通过给定节点找到这个数到根节点
- 初始化,把每个点所在集合初始化为其自身。通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
- 查找,查找元素所在的集合,即根节点。
- 合并,将两个元素所在的集合合并为一个集合。通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
代码如下:
static class UnionFind { Integer[] parent; public UnionFind(int n) { parent = new Integer[n]; } public Integer find(int v) { int temp = v; while (parent[temp] != null) { temp = parent[temp]; } return temp; } public void merge(int v0, int v1) { int root0 = find(v0); int root1 = find(v1); if (root0 > root1) { parent[root1] = root0; } else if (root0 < root1) { parent[root0] = root1; } } }
并查集优化
上面并查集代码的实现其实没有考虑查询的深度,这个深度会导致查询耗时过高的问题,具体描述如下图所示:
从深度来分析,一定要采用方案二才能减小遍历的次数。具体实现时需要如下考虑:
- 如果层数一致,则取其中一个节点作为根节点,并对根节点的层数加一。
- 否则选取层数更大的节点,将这个节点作为根节点继续处理。
代码实现如下:
static class UnionFind { Integer[] parent; int[] rank; public UnionFind(int n) { parent = new Integer[n]; rank = new int[n]; } public Integer find(int v) { int temp = v; while (parent[temp] != null) { temp = parent[temp]; } return temp; } public void merge(int v0, int v1) { int root0 = find(v0); int root1 = find(v1); if (root0 != root1) { if (rank[root0] > rank[root1]) { parent[root1] = root0; } else if (rank[root1] > rank[root0]) { parent[root0] = root1; } else { parent[root0] = root1; rank[root1]++; } } } }
代码实现
import java.util.HashMap;
import java.util.Map;
class Solution {
public int largestComponentSize(int[] nums) {
UnionFind uf = new UnionFind(maxNum(nums) + 1);
for (int num : nums) {
int sqrt = (int) Math.sqrt(num);
for (int i = 2; i <= sqrt; i++) {
if (num % i == 0) {
int v1 = i;
int v2 = num / i;
if (v1 == v2) {
uf.merge(num, v1);
} else {
uf.merge(num, v1);
uf.merge(num, v2);
}
}
}
}
Map<Integer, Integer> countMap = new HashMap<>();
int max = 1;
for (int num : nums) {
int root = uf.find(num);
int count = countMap.getOrDefault(root, 0);
count++;
max = Math.max(max, count);
countMap.put(root, count);
}
return max;
}
int maxNum(int[] nums) {
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
max = Math.max(max, nums[i]);
}
return max;
}
static class UnionFind {
Integer[] parent;
int[] rank;
public UnionFind(int n) {
parent = new Integer[n];
rank = new int[n];
}
public Integer find(int v) {
int temp = v;
while (parent[temp] != null) {
temp = parent[temp];
}
return temp;
}
public void merge(int v0, int v1) {
int root0 = find(v0);
int root1 = find(v1);
if (root0 != root1) {
if (rank[root0] > rank[root1]) {
parent[root1] = root0;
} else if (rank[root1] > rank[root0]) {
parent[root0] = root1;
} else {
parent[root0] = root1;
rank[root1]++;
}
}
}
}
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.largestComponentSize(new int[]{
2, 3, 6, 7, 4, 12, 21, 39
}));
}
}
总结
这道题挺好的,既要解决公因数,又要做并查集,最后还要做优化,才能完整解决这个问题。第一次做只能完成解决公因数和并查集,对并查集的优化也是看其他的代码分析才最终解决的。