Leetcode Daily Challenge Week 5: August 29th - August 31st: Largest Component Size by Common Factor

一、概述

输入一个序列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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值