leetcode 952. 按公因数计算最大组件大小(质数筛+并查集)

给定一个由不同正整数的组成的非空数组 A,考虑下面的图:
有 A.length 个节点,按从 A[0] 到 A[A.length - 1] 标记;
只有当 A[i] 和 A[j] 共用一个大于 1 的公因数时,A[i] 和 A[j] 之间才有一条边。
返回图中最大连通组件的大小。
输入:[4,6,15,35]
输出:4

总结一下题目的意思,若a和b有一个大于1的公因数,则a和b属于1组,假如a和b一组,b和c一组,那么a和c也是一组。

很显然是一个并查集的题目,这题的关键是确定任意两个数是否属于一组,确定之后就是一个并查集的裸题了。

判断任意两个数是否有大于1的公因数,最好想的暴力是两次遍历数组,对于每个不同的a,b,用一次gcd(a,b)求最大公因数,这样时间复杂度是O(n^2logn),这道题的n是20000,显然超时。

另一个比较好想的办法是枚举质因数,题目规定数组中的每个数都不大于100000,使用质数筛可以枚举小于100000的所有质数,大约有10000个,然后作为公因数去枚举这些质因数,这样时间复杂度变成了O(10000*n),还是超时。

再接着考虑到*如果m = p1p2pn(n>1),m是数组中的数,那么min(p1,p2,…,pn)不会超过sqrt(100000)≈350,原因很显然如果最小的p比350要大,那么p*p就已经大于题目中规定的最大值100000了,显然不符合题意。

考虑到任意两个数的质因数要么不大于350,要么大于350,所以我们先考虑公因数不大于350的情况,这种情况很好解决,将小于350的质数作为公因数枚举一下数组中所有的数字就能解决,如果遇到了两个数有共同的因数则用并查集合并一下即可。

到目前为止都比较好想到,而下一步卡了我很久,就是如果两个数唯一的质因数大于350怎么办,我们不可能枚举所有不大于100000的质因数,这会超时,但如果不去枚举所有的质因数,我们就没办法判断两个数是否属于一个集合。

!!!!!!!!!!!!!!!!!!!!!!!!!

每个数不小于350的质因数最多只有一个,这个刚才已经说过了,所以我们如果把一个数小于350的质因数全部排除,那么最后剩下的就是这个大于350的质因数。

排除不大于350的质因数可以在刚才的遍历中解决,如果某个质数是数组中某个数的质因数,那么就用数组中的数一直除以这个质数,这样遍历完所有的质数,数组中剩下的数只有两种可能,要么是1,要么是一个大于350的质数,用一个occur[100000]标记数组中是否存在某个质因数,如果当前数为n,之前occur[n]!=0,说明之前有一位和当前位拥有一个相同的大于350的质因数,再合并即可。

class Solution {
public:
    int not_prime[405];
    int prime[405];
    int father[20005];
    int sum[20005];
    int occur[100005];
    int find(int x)
    {
        if(father[x]==x)
            return x;
        int fx = find(father[x]);
        father[x] = fx;
        return fx;
    }
    int largestComponentSize(vector<int>& A) {
        not_prime[1] = 1;
        for(int i=2;i<=400;i++)
        {
            if(not_prime[i]==1)
                continue;
            for(int j=i+i;j<=400;j+=i)
                not_prime[j] = 1;
        }
        int cnt = 0;
        for(int i=1;i<=400;i++)
        {
            if(not_prime[i]==0)
                prime[++cnt] = i;
        }
        int ans = 1;
        int size = A.size();
        for(int i=0;i<size;i++)
        {
            father[i] = i;
            sum[i] = 1;
        }
        for(int i=1;i<=cnt;i++)
        {
            int pre = -1;
            for(int j=0;j<size;j++)
            {
                if(A[j]%prime[i]==0)
                {
                    if(pre==-1)
                        pre = j;
                    else
                    {
                        int fx = find(pre);
                        int fy = find(j);
                        if(fx!=fy)
                        {
                            father[fx] = fy;
                            sum[fy]+=sum[fx];
                            ans = max(ans,sum[fy]);
                        }
                        pre = j;
                    }
                    while(A[j]%prime[i]==0)
                        A[j]/=prime[i];
                }
            }
        }
        for(int i=0;i<size;i++)
        {
            if(A[i]==1)
                continue;
            if(occur[A[i]]==0)
            {
                occur[A[i]] = i+1;
                continue;
            }
            else
            {
                int fx = find(occur[A[i]] - 1);
                int fy = find(i);
                if(fx!=fy)
                {
                    father[fx] = fy;
                    sum[fy]+=sum[fx];
                    ans = max(ans,sum[fy]);
                }
                occur[A[i]] = i+1;
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值