一、概述
输入一个序列A,其中所有有公因子的数属于同一组,问最大的组中有多少元素。
二、分析
这道题我在最开始的思路出了一点问题。
当然是用并查集还是看出来了。
我的思路是:公因子?比如公因子有4,那么公因子必然有2,有9必然有3,因此我们不需要去遍历所有整数,遍历所有质因数就可以了。
A中元素最大是100000,一共有近10000质数,我只遍历这10000个就好了。
然后是使用并查集:
并查集的主要思想,是给每一个元素一个标签。根据所给的其他条件的不同,标签可以合并。并查集主要就是解决这个问题。它合并的速度很快。
那么我是这样想的:
并查集合并的是质数集合,也就是说,初始每个质数的标签是它自己。随后不断合并。如何合并呢?
对于A中的每一个元素i,找它所有的质因数,这些所有的质因数都属于一组,比如6的质因数有2和3,那么质数2和3就可以合为一组;33的质因数有3和11,那么3和11就可以合为一组。因此2,3,11就可以合为一组。因为并查集中father的传递性。它们的father都可以是2。
这有什么用呢?
在遍历完A之后,我们就知道了哪些质因数属于同一组。比如上面我们知道了3和11属于一组,那么6和121就属于一组。这就是题目所要求的。再遍历一遍A就可以得到各组的元素数量了。
这个算法的确可行。看看时间复杂度。
遍历A是O(n),遍历所有质因数是O(n),这个没法减少,因为一个质数只有它本身,这个质数可能逼近100000,那么就要乖乖遍历所有小于100000的质数,这个量级上面说了,10000,0.1n,常数舍去是n。
也就是说,时间复杂度O(n^2)。
所以超时了。
怎么优化呢?
我选用质因数是想不用遍历O(n)次了,但O(0.1n)也是O(n)啊。这是最大的败笔。
优化也要在这里优化。有没有什么方法,不用对一个元素m遍历O(m)次呢?这就是核心。
有。
在我的循环中,每次找到一个质因数,然后把它合并到第一个中去。质因数怎么得到呢?看是否整除——因此,我以为一次找到一个质因数没错,但一次应该是找到两个因数:质因数c和非质因数m/c。
也就是说,只要我不纠结于因数是否是质数,就可以把一次遍历压缩到O(logn)。
这样就解决了。
三、总结
并查集这个应用很灵活,比如我merge(a,b)不好实现,我可以merge(a,c)再merge(b,c)。当然这个c本身不能影响集合性质,,可以选择必定属于该集合的元素。本题就是这样,使用A[i]起到提纲挈领的作用,A[i]的每一个因数都和A[i]进行merge,而不是因数之间互相merge。
PS:
我超时的代码:
class Solution {
int MAXN=100010;
int fa[100010];
int num[100010]={0};
vector<int> prime;
inline void init(int n)
{
for (int i = 1; i < n; ++i)
fa[i] = i;
}
int find(int x)
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
inline void merge(int i, int j)
{
fa[find(j)] = find(i);
}
bool isPrime(int n)
{
int i;
for(i = 2; i*i <= n; i++) {
if((n % i) == 0) // 如果能被除了1和它本身的数整除,就不是素数
return false;
}
return true; // 是素数
}
void addPrime()
{
for(int i=2;i<=100010;i++)
{
if(isPrime(i))
prime.push_back(i);
}
}
public:
int largestComponentSize(vector<int>& A) {
init(100010);
addPrime();
int existOne=0;
for(int i=0;i<A.size();i++)
{
if(A[i]==0)
existOne=1;
int first=-1;
for(int j=0;prime[j]<=A[i];j++)//遍历A[i]次
{
if(A[i]%prime[j]==0)
{
if(first==-1)
{
first=prime[j];
}
merge(first,prime[j]);//一次找到一个质因数,然后merge
}
}
}
int res=INT_MIN;
for(int i=0;i<A.size();i++)
{
for(int j=0;prime[j]<=A[i];j++)
{
if(A[i]%prime[j]==0)
{
num[find(prime[j])]++;
res=res>num[fa[prime[j]]]?res:num[fa[prime[j]]];
break;
}
}
}
return res+existOne;
}
};
不超时的算法:
class Solution {
int fa[100010],rank[100010];
int num[100010]={0};
inline void init(int n)
{
for (int i = 1; i < n; ++i)
{
fa[i] = i;
rank[i] = 1;
}
}
int find(int x)
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
inline void merge(int i, int j)
{
int x = find(i), y = find(j); //先找到两个根节点
if (rank[x] <= rank[y])
fa[x] = y;
else
fa[y] = x;
if (rank[x] == rank[y] && x != y)
rank[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1
}
public:
int largestComponentSize(vector<int>& A) {
init(100010);
for(int i=0;i<A.size();++i)
{
for(int j=2;j*j<=A[i];++j)
{
if(A[i]%j==0)
{
merge(A[i],j);//一次找到俩
merge(A[i],A[i]/j);
}
}
}
int res=INT_MIN;
for(int i=0;i<A.size();i++)
{
res=max(res,++num[find(A[i])]);
}
return res;
}
};