leetcode刷题日记-【952. 按公因数计算最大组件大小】

206 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

题目描述

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

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

 

示例 1:

image.png

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

image.png

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

image.png

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

题目元素

  • 查询给定数组中的最大连通组件长度;
  • 连通的定义,两个数有大于1的公因数才能被连通,即连通组件中相邻的两个元素都一定会有公因数,涉及到连通组件的操作可以采用并查集;
  • 查找公因数,首先要查找范围内的数的所有的质数。整数分为合数、质数、1.合数是指除了本身和1之外还有其它的因数。质数是指除了本身和1外没有其它的因数;
  • 所以题目给定的数组如果包含质因数,则这个节点一定不能被其它节点连通。除此之外,其它所有的节点想要连通,那么两个节点之间一定有一个公共质因数。所以这道题的难点在于计算质因数和连通查找。

解题思路

  • 欧拉筛 找到一个素数后,就将它的倍数标记为合数,也就是把它的倍数“筛掉”;如果一个数没有被比它小的素数“筛掉”,那它就是素数。欧拉筛的区别就是只被最小质数“筛掉”。
  • 并查集 没有交集的集合的操作,包括将两个集合合并和判断两个节点是否在同一个集合里。主要用于在图类算法中计算动态连通性。

代码实现

public int largestComponentSize(int[] nums) {
    int maxNum = Arrays.stream(nums).max().getAsInt();
    // 存放改下标对应的值是否是质数,如primeFlag[2] 代表2是否是质数,true为合数,false为质数
    boolean[] primeFlag = new boolean[maxNum + 1];
    // 存放(2,maxNum)之间所有的质数
    int[] primes = new int[maxNum];
    for (int i=2; i <= maxNum;i ++) {
        // 查找范围内的质数
        if (!primeFlag[i]){
            // 放入质数中 primes[0]用于存储数组primes的真实长度,因为在创建时无法确定数据的质数个数
            primes[++primes[0]] = i;
        }
        // 如果当前节点是质数,则它的倍数都不是质数
        // 遍历已存在的质数,用当前节点乘以质数,获取到的数一定不是质数,终止条件为primes数组长度且这个乘积小于最大数(大于的数无意义)
        for (int j = 1;j <= primes[0] && i * primes[j] <= maxNum;j++){
            // 置为合数
            primeFlag[i * primes[j]] = true;
            // 欧拉筛只被它的最小质因数筛出,所以当下一个质数能够被当前节点整除时,代表当前已经不是最小质因数,可以直接进行下一次质数筛出
            if (i % primes[j] == 0) {
                break;
            }
        }
    }
    // 初始化并查集
    UnionFind unionFind = new UnionFind(maxNum);
    // 遍历元素,将元素与质因数合并起来
    for (int num : nums) {
        int quotient = num;
        for (int  j=1; j < primes[0] & primes[j] * primes[j] <= quotient; j++) {
            if (num % primes[j] == 0) {
                unionFind.union(num,primes[j]);
                while (quotient % primes[j] == 0) {
                    quotient /= primes[j];
                }
            }
        }
        if (quotient > 1) {
            unionFind.union(quotient, num);
        }
    }
    int[] sizeCount = new int[maxNum + 1];
    int maxSize = 0;
    for (int num : nums) {
        //计算每个合集中元素出现的次数
        maxSize = Math.max(maxSize,++ sizeCount[unionFind.find(num)]);
    }
    return maxSize;
}

public class UnionFind{

    // 每个节点对应的集合大小
    int[] size;
    // 存放每个节点的父节点
    int[] parent;

    /**
     * 将两个节点所属集合合并
     *
     * @param p 节点p
     * @param q 节点q
     */
    public void union(int p,int q){
        int topP = find(p);
        int topQ = find(q);
        if (topQ == topP){
            // 集合相同,无需合并
            return;
        }
        // 权衡集合大小,进行集合合并
        if (size[topP] > size[topQ]){
            // p集合的数量大于q
            parent[topP] = topQ;
            ++size[topQ];
        }else {
            parent[topQ] = topP;
            ++size[topP];
        }
    }

    /**
     * 查找节点p所在的集合的顶级父节点
     *
     * @param p 节点p
     * @return 节点p所在集合的顶级父节点
     */
    private int find(int p) {
        // 递归获取顶级节点
        // 路径压缩,将当前节点的父节点直接设置成父节点的父节点
        return parent[p] == p ? p : (parent[p] = find(parent[p]));
    }

    public UnionFind(int n){
        parent = new int[n+1];
        size = new int[n+1];
        for (int i = 0 ; i <= n ; i++) {
            parent[i] = i;
            size[i] = 1;
        }

    }
}