【C/C++】952. 按公因数计算最大组件大小

693 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情


题目链接:952. 按公因数计算最大组件大小

题目描述

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

  • 有 nums.length 个节点,按从 nums[0] 到 nums[nums.length - 1] 标记;
  • 只有当 nums[i] 和 nums[j] 共用一个大于 1 的公因数时,nums[i] 和 nums[j] 之间才有一条边。

返回 图中最大连通组件的大小

提示:

  • 1nums.length21041 \leqslant nums.length \leqslant 2 * 10^4
  • 1nums[i]1051 \leqslant nums[i] \leqslant 10^5
  • nums 中所有值都 不同

示例 1:

ex1.png

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

示例 2:

ex2.png

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

示例 3:

ex3.png

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

整理题意

题目给定一个正整数数组 nums,规定数组中任意两个数如果公约数大于 1 那么这两个整数之间有一条无向边。题目要求返回图中最大连通块的元素个数。

解题思路分析

由于题目数据范围较大,建图所需的时间就已经 TLE 超时了,所以我们无法通过建图再遍历的方法来完成。

通过联通组件可以想到连通块,连通块又可以想到并查集,因为并查集是用来处理连通块的。

  • 那么首先考虑如何进行并操作,由于数据范围较大的原因,无法对数组中的整数进行俩俩判断。
  • 那么考虑对于数组中的每一个整数来说,我们可以找到当前整数所有大于等于 2 的因子,将当前整数与这些因子进行并集操作(由于整数最大为 10510^5,寻找因子的时间复杂度为 O(nlogn)O(n \log n),是可以接受的),也就是当前整数与它的所有大于等于 2 的因子都是连通组件。

通过这样的并集操作,可以将数组中的所有整数进行分类,并且是通过他们的因子来进行连接的。

最后遍历数组中的所有正整数,查找当前这个整数属于哪个集合,维护集合大小,最后输出集合最大值即可。

具体实现

  1. 将每个整数与它的因子(大于等于 2 的因子)进行连接(并集操作);
  2. 通过并查集算法可以得到连接后的每个连通块;
  3. 遍历整数数组,记录每个连通块中包含整数的个数,同时维护最大值即可。

复杂度分析

  • 时间复杂度:O(n×α(n)×m)O(n \times \alpha(n) \times \sqrt{m}),其中 n 是数组 nums 的长度,m 是数组 nums 中的最大元素,α\alpha 是反阿克曼函数。这里的并查集使用了路径压缩,单次操作的时间复杂度是 O(α(n))O(\alpha(n)),对于每个元素需要遍历 O(m)O(\sqrt{m}) 个数字寻找公因数并执行合并操作,总操作次数是 O(n×m)O(n \times \sqrt{m}),因此整个数组的并查集操作的时间复杂度是 O(n×α(n)×m)O(n \times \alpha(n) \times \sqrt{m}),并查集操作之后需要 O(n×α(n))O(n \times \alpha(n)) 的时间再次遍历数组计算最大组件大小,因此总时间复杂度是 O(n×α(n)×m)O(n \times \alpha(n) \times \sqrt{m})
  • 空间复杂度:O(m)O(m),其中 m 是数组 nums 中的最大元素。并查集和统计组件大小都需要 O(m)O(m) 的空间。

代码实现

class Solution {
    // 并查集模板
    vector<int> father;
    // 查找 x 所在集合的根节点
    int Find(int x){
        return father[x] == x ? x : father[x] = Find(father[x]);
        // father[x] = Find(father[x]) 为路径压缩操作
        // 这样可以使得查找效率提升
    }
    // 将 x 所在的集合和 y 所在的集合进行合并操作
    void Union(int x, int y){
        int fx = Find(x);
        int fy = Find(y);
        if(fx != fy) father[fx] = fy;
    }
public:
    int largestComponentSize(vector<int>& nums) {
        // 找到 nums 数组中的最大值 m
        int m = 0;
        for(int &num : nums) m = max(m, num);
        // 初始化并查集 father 数组
        father.resize(m + 1);
        for(int i = 0; i <= m; i++) father[i] = i;
        // 将 [2, m] 之内的所有整数进行归类
        // 也就是将公约数大于 1 的放入一个集合
        for(int &num : nums){
            // 找到 num 所有大于等于 2 的因子将其与 num 放入一个集合
            for(int i = 2; i * i <= num; i++){
                // i 为 num 的因子
                if(num % i == 0){
                    Union(num, i);
                    // num / i 也同为 num 的因子
                    Union(num, num / i);
                }
            }
        }
        // 统计每个集合中包含数组中元素的个数
        // 这里使用数组比哈希表更节省空间和时间
        int mp[m + 1];
        memset(mp, 0, sizeof(mp));
        int res = 0;
        for(int &num : nums){
            // 找到 num 所在集合的根节点,记录该集合个数
            mp[Find(num)]++;
            // 维护答案最大值
            res = max(res, mp[Find(num)]);
        }
        return res;
    }
};

总结

  • 该题很容易想到使用 并查集,但难点在于思考如何将这些整数进行连接,核心的连接方法是:通过每个数大于等于 2 的因子来进行连接的
  • 并查集的 Find() 函数需要注意使用路径压缩来提高查找效率。
  • 在寻找一个整数 x 的因子时我们只需要遍历到 x\sqrt{x} 的位置即可,这时因为:如果 ix 的因子,那么 x / i 也为 x 的因子。
  • 测试结果:

952.按公因数计算最大组件大小.png

结束语

与其幻想一蹴而就的成功,不如积累一点一滴的进步。认真做好眼下的每一件事,享受每一个小成就带来的喜悦,去遇见因为努力而变得更美好的自己。新的一天,加油!