• 并查集介绍和基于并查集解决问题——LeetCode 952 按公因数计算最大组件大小


    题目内容

    给定一个由不同正整数的组成的非空数组 nums ,考虑下面的图:

    有 nums.length 个节点,按从 nums[0] 到 nums[nums.length - 1] 标记;
    只有当 nums[i] 和 nums[j] 共用一个大于 1 的公因数时,nums[i] 和 nums[j]之间才有一条边。
    返回 图中最大连通组件的大小 。

    示例 1:

    在这里插入图片描述

    输入:nums = [4,6,15,35]
    输出:4

    示例 2:

    在这里插入图片描述

    输入:nums = [20,50,9,63]
    输出:2

    示例 3:

    在这里插入图片描述

    输入:nums = [2,3,6,7,4,12,21,39]
    输出:8

    提示:

    1 <= nums.length <= 2 * 104
    1 <= nums[i] <= 105
    nums 中所有值都 不同

    来源:力扣(LeetCode
    链接:https://leetcode.cn/problems/largest-component-size-by-common-factor
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    解题思路

    讲真,这道题一开始很难没思路,至少这个暴力是看的见的,不过很明显的一个问题就是数据规模太大了,这个规模我们要每一个求因子,然后两两一组去比是必不能行的,后来我们考虑了哈希表,感觉也不太行,于是就依据题解选择了并查集

    这是我之前学习并查集时候的笔记(学完了真的,要code一下,不然害人害己 T^T)

    并查集,常见重要的一个结构
    功能1:返回两个元素是否在一个集合中。
    功能2:集合的合并

    这个功能实现好像还好是吧?但是实际上我们实现出来的时间复杂度八成会超标(链表功能1不好,哈希表功能2不好,时间比较久),这就是并查集的大优势,他对于上面两个功能都是O(1)级别的。

    并查集功能1思路:一直回溯,以第一个元素作为标志。如果两个元素去查看,发现标志元素一致,就说明在一个集合里,反之不在。

    功能2思路:先功能1,之后合并就是简单的挂靠,可以想象成链表拼接那种感觉,不过更像是树或者说图树枝的延伸。一般是少顶的挂在多的顶部底下。

    并查集为什么那么好,因为他的实现还有优化。
    功能1:本意是要我们一直往上,找到头去对比,不过我们第一个元素走到头之后,第二个元素的时候就不用重复走第一个元素的路了,我们把第一个元素走过的路都扁平化,也就是途中节点直接走到终点。

    然后我们大概了解了,并查集的功能了,我们来看下这道题是如何通过并查集来实现的。这里我们也是通过画图来看一下:
    在这里插入图片描述

    就是说,我们是通过其他节点以我们nums的数据作为代表节点进行合并操作的方式实现的两个节点之间的联系,同时完成更新代表节点,不过这个时候我心里有一个想法就是修改并查集的合并机制,我让所有新来的节点作为头,叫大子树拼接过来,然后统计rank-1。然后,果不其然,这个是有问题的,并且问题多到不知能从何开始说起了,主要原因我觉得是在于节点2,2这个节点的因子判断会导致一些问题,具体的。。我写代码的时候遇到了一些杂七杂八的,三言两语也说不太清,还是走并查集正道的好。

    这里我最后是学习了题解中的经验,用容器存储了每一个节点的代表节点的出现次数,以这个作为统计并查集中节点数的标准确实是最好的,这个可能是一个普遍的技巧吧,不过对于我这个只对于并查集有一定概念了解的新人来说,还是很巧妙有帮助的。

    不管咋说,做一个困难的题目,还是挺happy的,毕竟之前没少被折磨。

    代码

    class Solution {
    public:
        vector<int> pre;     					//存储每个结点的前驱结点 
        vector<int> rank;    					//树的高度 
        void init(int n)     				//初始化函数,对录入的 n个结点进行初始化 
        {
            for(int i = 0; i < n; i++){
                pre.push_back(i);     			//每个结点的上级都是自己 
                rank.push_back(1);    			//每个结点构成的树的高度为 1 
            } 
        }
     
        int find(int x)     				
        {
            if(pre[x] == x){
               return x; 
            }	
            pre[x] = find(pre[x]);	 
            return pre[x];   
        } 
    
        bool isSame(int x, int y)      		//判断两个结点是否连通 
        {
            return find(x) == find(y);  	//判断两个结点的根结点(即代表元)是否相同 
        }
    
        bool join(int x,int y)
        {
            x = find(x);						//寻找 x的代表元
            y = find(y);						//寻找 y的代表元
            if(x == y) return false;			//如果 x和 y的代表元一致
            if(rank[x] > rank[y]) pre[y]=x;		
            else								
            {
                if(rank[x]==rank[y]) rank[y]++;	
                pre[x]=y;						
            }
            // pre[y]=x;
            // rank[x]=max(rank[x],rank[y]+1);
            // cout<
            return true;						//返回 true,表示合并成功
        }
    
        int largestComponentSize(vector<int>& nums) {
            int l=nums.size();
            int max=*max_element(nums.begin(),nums.end());
            init(max+1);
            for(int i=0;i<l;i++){
                int shu=nums[i];
                // if(shu==2){
                //     join(shu,1);
                //     continue;
                // }
                for(int j=2;j*j<=shu;j++){
                    if(shu%j==0){
                        join(shu,j);
                        join(shu,shu/j);
                    }
                }
            }
            int ans=0;
            vector<int> count(max+1);
            for(int i=0;i<l;i++){
                cout<<i<<"  "<<rank[nums[i]]<<endl;
                int tou=find(nums[i]);
                count[tou]++;
                ans=ans>count[tou]?ans:count[tou];
            }
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
  • 相关阅读:
    【课程作业】西瓜书 机器学习课后习题 : 第十二章
    网络编程牛牛牛牛牛
    spring-boot 单元测试 faq2
    只需一行Python代码即可玩20几款小游戏
    如何确定你访问的网站的真实性——证书体系
    异步 IO 机制 io_uring
    6.DesignForPlacement\PlaceHighlightedSymbols
    RIP 路由 3 个定时器的工作流程和 4 种防环方法
    Go语言代码断行规则详解
    如何用Java设计自动售货机?
  • 原文地址:https://blog.csdn.net/weixin_51529433/article/details/126072430