Skip to content

Commit 0cdcf0f

Browse files
add solution 0952
1 parent 4f2db82 commit 0cdcf0f

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# 952.按公因数计算最大组件大小
2+
3+
## 题目描述
4+
5+
给定一个由不同正整数的组成的非空数组 `A`,考虑下面的图:
6+
7+
-`A.length` 个节点,按从 `A[0]``A[A.length - 1]` 标记;
8+
- 只有当 `A[i]``A[j]` 共用一个大于 1 的公因数时,`A[i]``A[j]` 之间才有一条边。
9+
10+
返回图中最大连通组件的大小。
11+
12+
13+
14+
**示例 2:**
15+
16+
输入:[4,6,15,35]
17+
输出:4
18+
19+
![示例](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/01/ex1.png)
20+
21+
**提示:**
22+
23+
1. `1 <= A.length <= 20000`
24+
2. `1 <= A[i] <= 100000`
25+
26+
## 解法
27+
28+
### Naive 版本
29+
30+
这道题涉及到画连线,应当涉及到 union-find。初步解法是:
31+
32+
* 使用数组,初始化各节点的 root 为自身,并且维护各节点 root 所连通的图的节点数量(size)为 1
33+
* 遍历数组中的每一个数,如果和其他数有大于 1 的公因数,则用 union 方法将他们连在一起
34+
* 在 union 的过程中,由于 union 的对象为各节点的根,因此需要使用 find 方法,并且缩短所涉及的节点和其根(root)的搜索距离,即将该节点与 root 直接连在一起。同时更新 size 数组的对应值
35+
* 在遍历结束后,遍历 size 数组,找到 size 最大的。
36+
37+
```java
38+
class Solution {
39+
public int largestComponentSize(int[] A) {
40+
int n = A.length;
41+
int[] root = new int[n];
42+
int[] size = new int[n];
43+
// 初始化 root 和 size array
44+
for (int i = 0; i < n; i++) {
45+
root[i] = i;
46+
size[i] = 1;
47+
}
48+
for (int i = 0; i < n; i++) {
49+
for (int j = i + 1; j < n; j++) {
50+
if (!isCoprime(A[i], A[j])) {
51+
union(size, root, i, j);
52+
}
53+
}
54+
}
55+
int max = 0;
56+
for (int i = 0; i < n; i++) {
57+
max = Math.max(size[i], max);
58+
}
59+
return max;
60+
}
61+
62+
public void union(int[] size, int[] root, int i, int j) {
63+
int rootI = find(root, i);
64+
int rootJ = find(root, j);
65+
if (rootI == rootJ) {
66+
// 它们已经属于同一个 root
67+
return;
68+
}
69+
// 决定两个节点如何连接和 size 的更新
70+
if (size[rootI] > size[rootJ]) {
71+
root[rootJ] = rootI;
72+
size[rootI] += size[rootJ];
73+
} else {
74+
root[rootI] = rootJ;
75+
size[rootJ] += size[rootI];
76+
}
77+
}
78+
79+
public int find(int[] root, int i) {
80+
// 当某节点的根不是他自己时,则需要继续找到其 root
81+
List<Integer> records = new LinkedList<>();
82+
while (root[i] != i) {
83+
records.add(i);
84+
i = root[i];
85+
}
86+
// 将这些节点均指向其 root
87+
for (Integer record: records) {
88+
root[record] = i;
89+
}
90+
91+
return i;
92+
}
93+
94+
public boolean isCoprime(int x, int y) {
95+
// 检查 x,y 是否互质
96+
if (x == 1 || y == 1) {
97+
return true;
98+
}
99+
while (true) {
100+
int temp = x % y;
101+
if (temp == 0) {
102+
if (y == 1) {
103+
return true;
104+
}
105+
return false;
106+
}
107+
x = y;
108+
y = temp;
109+
}
110+
}
111+
}
112+
```
113+
114+
但是这个代码其实会超时,因为中间的遍历逻辑会耗费很长的时间,时间复杂度为 O(n^2)。因此我们需要更快一点的解法。
115+
116+
### 优化版本
117+
118+
由于连通节点的条件是两个节点有公因数,那么他们可以通过这个公因数连在一起,而这个公因数又可以被分解为质因数,这样,我们只需要知道一个节点的质因数有哪些,并且将这些质因数和该节点相连。则对于每一个节点,我们都连接的是其质因数,或者说是质因数所对应的节点,但是本质上我们把这些有相同质因数的节点都连在了一起。具体步骤为:
119+
120+
* 维护 prime set,找到 100000 以内所有质数(找质数的方法应该都会吧)
121+
* 维护三个数组,分别为:
122+
* 各节点所连接的 root 编号,初始化为节点本身的编号
123+
* 各节点为 root 时,连通图的 size,初始化为 1
124+
* 各质数所连接到的节点对应的 root 的编号,初始化为 -1(因为开始时这些质数都没有和节点连在一起)
125+
* 遍历节点,其中遍历所有质数,如果节点可以整除质数,则将该质数所连通的节点(如果有的话)和当前节点连在一起;并且更新该质数连通 到 新的连通图的 root 的编号。同时更新 root 对应的 size
126+
* 遍历 size 数组,找到值最大的集合
127+
128+
而题中给定了节点大小小于 100000,因此我们只需要找到 100000 里面的所有质数,并遍历节点将其连接到可以整除该节点的质数上,就等于是完成了有公因数之间的节点的连通。而根据我们上面的推算,遍历每个节点的所有质数时间复杂度是确定的为 O(np),p 为 100000 以内质数数量,即为 O(n),而 union-find 方法的每一个步骤 amortized 复杂度为 O(log*n),一个远小于 log n 的值。因此,我们通过优化了寻找连通边的方法,来达到优化算法的目的。
129+
130+
```java
131+
class Solution {
132+
public int largestComponentSize(int[] A) {
133+
int n = A.length, num = 100000 + 1, max = 0;
134+
Set<Integer> primes = findPrime(num);
135+
int[] root = new int[n];
136+
int[] size = new int[n];
137+
int[] primeToNode = new int[num];
138+
// 一开始 prime 没有和数组 A 中的 node 连在一起
139+
Arrays.fill(primeToNode, -1);
140+
// 初始化 root 和 size array
141+
for (int i = 0; i < n; i++) {
142+
root[i] = i;
143+
size[i] = 1;
144+
}
145+
for (int i = 0; i < n; i++) {
146+
int curr = A[i];
147+
// find all of its prime factors
148+
for (Integer prime: primes) {
149+
if (primes.contains(curr)) {
150+
prime = curr;
151+
}
152+
if (curr % prime == 0) {
153+
// 我们为 curr 找到一个质因数,则需要将该节点加入该 prime 已经连接到的根节点上
154+
if (primeToNode[prime] != -1) {
155+
// 该 prime 已经与数组 A 中 node 相连
156+
union(size, root, primeToNode[prime], i);
157+
}
158+
primeToNode[prime] = find(root, i);
159+
while (curr % prime == 0) {
160+
// 将质因数 prime 全部剔除
161+
curr = curr / prime;
162+
}
163+
}
164+
if (curr == 1) {
165+
break;
166+
}
167+
}
168+
}
169+
for (int i = 0; i < n; i++) {
170+
max = Math.max(size[i], max);
171+
}
172+
return max;
173+
}
174+
175+
public Set<Integer> findPrime(int num) {
176+
boolean[] isPrime = new boolean[num];
177+
Arrays.fill(isPrime, true);
178+
Set<Integer> primes = new HashSet<>();
179+
for (int i = 2; i < isPrime.length; i++) {
180+
if (isPrime[i]) {
181+
primes.add(i);
182+
for (int j = 0; i * j < isPrime.length; j++) {
183+
isPrime[i * j] = false;
184+
}
185+
}
186+
}
187+
return primes;
188+
}
189+
190+
public void union(int[] size, int[] root, int i, int j) {
191+
int rootI = find(root, i);
192+
int rootJ = find(root, j);
193+
if (rootI == rootJ) {
194+
// 它们已经属于同一个 root
195+
return;
196+
}
197+
if (size[rootI] > size[rootJ]) {
198+
root[rootJ] = rootI;
199+
size[rootI] += size[rootJ];
200+
} else {
201+
root[rootI] = rootJ;
202+
size[rootJ] += size[rootI];
203+
}
204+
}
205+
206+
public int find(int[] root, int i) {
207+
// 当某节点的根不是他自己时,则需要继续找到其 root
208+
List<Integer> records = new LinkedList<>();
209+
while (root[i] != i) {
210+
records.add(i);
211+
i = root[i];
212+
}
213+
// 将这些节点均指向其 root
214+
for (Integer record: records) {
215+
root[record] = i;
216+
}
217+
218+
return i;
219+
}
220+
}
221+
```
222+
223+
224+
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
class Solution {
2+
public int largestComponentSize(int[] A) {
3+
int n = A.length, num = 100000 + 1, max = 0;
4+
Set<Integer> primes = findPrime(num);
5+
int[] root = new int[n];
6+
int[] size = new int[n];
7+
int[] primeToNode = new int[num];
8+
// 一开始 prime 没有和数组 A 中的 node 连在一起
9+
Arrays.fill(primeToNode, -1);
10+
// 初始化 root 和 size array
11+
for (int i = 0; i < n; i++) {
12+
root[i] = i;
13+
size[i] = 1;
14+
}
15+
for (int i = 0; i < n; i++) {
16+
int curr = A[i];
17+
// find all of its prime factors
18+
for (Integer prime: primes) {
19+
if (primes.contains(curr)) {
20+
prime = curr;
21+
}
22+
if (curr % prime == 0) {
23+
// 我们为 curr 找到一个质因数,则需要将该节点加入该 prime 已经连接到的根节点上
24+
if (primeToNode[prime] != -1) {
25+
// 该 prime 已经与数组 A 中 node 相连
26+
union(size, root, primeToNode[prime], i);
27+
}
28+
primeToNode[prime] = find(root, i);
29+
while (curr % prime == 0) {
30+
// 将质因数 prime 全部剔除
31+
curr = curr / prime;
32+
}
33+
}
34+
if (curr == 1) {
35+
break;
36+
}
37+
}
38+
}
39+
for (int i = 0; i < n; i++) {
40+
max = Math.max(size[i], max);
41+
}
42+
return max;
43+
}
44+
45+
public Set<Integer> findPrime(int num) {
46+
boolean[] isPrime = new boolean[num];
47+
Arrays.fill(isPrime, true);
48+
Set<Integer> primes = new HashSet<>();
49+
for (int i = 2; i < isPrime.length; i++) {
50+
if (isPrime[i]) {
51+
primes.add(i);
52+
for (int j = 0; i * j < isPrime.length; j++) {
53+
isPrime[i * j] = false;
54+
}
55+
}
56+
}
57+
return primes;
58+
}
59+
60+
public void union(int[] size, int[] root, int i, int j) {
61+
int rootI = find(root, i);
62+
int rootJ = find(root, j);
63+
if (rootI == rootJ) {
64+
// 它们已经属于同一个 root
65+
return;
66+
}
67+
if (size[rootI] > size[rootJ]) {
68+
root[rootJ] = rootI;
69+
size[rootI] += size[rootJ];
70+
} else {
71+
root[rootI] = rootJ;
72+
size[rootJ] += size[rootI];
73+
}
74+
}
75+
76+
public int find(int[] root, int i) {
77+
// 当某节点的根不是他自己时,则需要继续找到其 root
78+
List<Integer> records = new LinkedList<>();
79+
while (root[i] != i) {
80+
records.add(i);
81+
i = root[i];
82+
}
83+
// 将这些节点均指向其 root
84+
for (Integer record: records) {
85+
root[record] = i;
86+
}
87+
88+
return i;
89+
}
90+
}

0 commit comments

Comments
 (0)