LeetCode探索(107):952-按公因数计算最大组件大小

1,446 阅读1分钟

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

题目

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

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

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

示例 1:

img

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

示例 2:

img

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

示例 3:

img

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

提示:

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

思考

本题难度困难。

首先是读懂题意。数字 a 和 b 属于同一个组件,意思是这两个数字是互相连接的,我们可以给这两个数字之间添加指向关系。使用数组 parent 记录数字的指向关系,开始时每个数字指向其自身。使用数组 rank 记录每个数字的连接次数,开始时每个数字的连接次数均为0。

求出数组 nums 中的最大值 m,对于每个不超过 m 的正整数 num,计算 num 和哪些数属于同一个组件。对于范围 [2, num\sqrt{num}] 内的每个正整数 i,如果 i 是 num 的因数,则 num 和 i、num/i 都属于同一个组件。

可以使用并查集实现组件的计算。初始时,每个数分别属于不同的组件。如果两个正整数满足其中一个正整数是另一个正整数的因数,则这两个正整数属于同一个组件,将这两个正整数的组件合并。当所有不超过 m 的正整数都完成合并操作之后,遍历数组 nums,对于每个数得到其所在的组件并更新该组件的大小,遍历结束之后即可得到最大组件的大小。

解答

方法一:并查集

/**
 * @param {number[]} nums
 * @return {number}
 */
var largestComponentSize = function(nums) {
  const m = Math.max(...nums)
  const uf = new UnionFind(m + 1)
  for (const num of nums) {
    for (let i = 2; i * i <= num; i++) {
      // 如果 i 是 num 的因数,则 num 和 i、num/i 都属于同一个组件
      if (num % i === 0) {
        uf.union(num, i)
        uf.union(num, Math.floor(num / i))
      }
    }
  }
  // 计数
  const counts = new Array(m + 1).fill(0)
  let ans = 0
  for (let num of nums) {
    const root = uf.find(num)
    counts[root]++
    ans = Math.max(ans, counts[root])
  }
  return ans
}
class UnionFind {
  constructor(n) {
    // parent记录数字的指向关系,开始时每个数字指向自己
    this.parent = new Array(n).fill(0).map((_, i) => i) // [0, 1, ..., n-1]
    // rank记录每个数字的连接次数, 比如 this.parent[2] = 4时, 2指向4, this.rank[4]++
    this.rank = new Array(n).fill(0)
  }
  union(x, y) {
    // (4, 2), (4, 2), (6, 2), (6, 3), (15, 3), (15, 5), (35, 5), (35, 7)
    let rootx = this.find(x)
    let rooty = this.find(y)
    if (rootx !== rooty) {
      if (this.rank[rootx] > this.rank[rooty]) {
        // 3. this.parent[3] = 4
        // 4. ...
        this.parent[rooty] = rootx
      } else if (this.rank[rootx] < this.rank[rooty]) {
        // 2. this.parent[6] = 4
        this.parent[rootx] = rooty
      } else {
        // 1. this.parent[2] = 4; this.rank[4]++
        this.parent[rooty] = rootx
        this.rank[rootx]++
      }
    }
  }
  find(x) {
    if (this.parent[x] !== x) {
      this.parent[x] = this.find(this.parent[x])
    }
    return this.parent[x]
  }
}

复杂度分析:

  • 时间复杂度:O(n × α(n) × m\sqrt{m}),其中 n 是数组 nums 的长度,m 是数组 nums 中的最大元素,α 是反阿克曼函数。
  • 空间复杂度:O(m),其中 m 是数组 nums 中的最大元素。并查集和统计组件大小都需要 O(m) 的空间。

参考