携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情 >>
题目
给定一个由不同正整数的组成的非空数组 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 * 1041 <= nums[i] <= 105nums中所有值都 不同
思考
本题难度困难。
首先是读懂题意。数字 a 和 b 属于同一个组件,意思是这两个数字是互相连接的,我们可以给这两个数字之间添加指向关系。使用数组 parent 记录数字的指向关系,开始时每个数字指向其自身。使用数组 rank 记录每个数字的连接次数,开始时每个数字的连接次数均为0。
求出数组 nums 中的最大值 m,对于每个不超过 m 的正整数 num,计算 num 和哪些数属于同一个组件。对于范围 [2, ] 内的每个正整数 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) × ),其中 n 是数组 nums 的长度,m 是数组 nums 中的最大元素,α 是反阿克曼函数。
- 空间复杂度:O(m),其中 m 是数组 nums 中的最大元素。并查集和统计组件大小都需要 O(m) 的空间。