Skip to content

Commit b1b0d01

Browse files
authored
Merge pull request #132 from biubiubiubiubiubiubiu/master
add solution 0952 [Java]
2 parents 509551d + 46c9982 commit b1b0d01

File tree

5 files changed

+329
-0
lines changed

5 files changed

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