leetcode
leetcode 1051 ~ 1100
带阈值的图连通性

带阈值的图连通性

难度:

标签:

题目描述

n 座城市,编号从 1n 。编号为 xy 的两座城市直接连通的前提是: xy 的公因数中,至少有一个 严格大于 某个阈值 threshold 。更正式地说,如果存在整数 z ,且满足以下所有条件,则编号 xy 的城市之间有一条道路:

  • x % z == 0
  • y % z == 0
  • z > threshold

给你两个整数 nthreshold ,以及一个待查询数组,请你判断每个查询 queries[i] = [ai, bi] 指向的城市 aibi 是否连通(即,它们之间是否存在一条路径)。

返回数组 answer ,其中answer.length == queries.length 。如果第 i 个查询中指向的城市 aibi 连通,则 answer[i]true ;如果不连通,则 answer[i]false

 

示例 1:

 

输入:n = 6, threshold = 2, queries = [[1,4],[2,5],[3,6]]
输出:[false,false,true]
解释:每个数的因数如下:
1:   1
2:   1, 2
3:   1, 3
4:   1, 2, 4
5:   1, 5
6:   1, 2, 3, 6
所有大于阈值的的因数已经加粗标识,只有城市 3 和 6 共享公约数 3 ,因此结果是: 
[1,4]   1 与 4 不连通
[2,5]   2 与 5 不连通
[3,6]   3 与 6 连通,存在路径 3--6

示例 2:

 

输入:n = 6, threshold = 0, queries = [[4,5],[3,4],[3,2],[2,6],[1,3]]
输出:[true,true,true,true,true]
解释:每个数的因数与上一个例子相同。但是,由于阈值为 0 ,所有的因数都大于阈值。因为所有的数字共享公因数 1 ,所以所有的城市都互相连通。

示例 3:

 

输入:n = 5, threshold = 1, queries = [[4,5],[4,5],[3,2],[2,3],[3,4]]
输出:[false,false,false,false,false]
解释:只有城市 2 和 4 共享的公约数 2 严格大于阈值 1 ,所以只有这两座城市是连通的。
注意,同一对节点 [x, y] 可以有多个查询,并且查询 [x,y] 等同于查询 [y,x] 。

 

提示:

  • 2 <= n <= 104
  • 0 <= threshold <= n
  • 1 <= queries.length <= 105
  • queries[i].length == 2
  • 1 <= ai, bi <= cities
  • ai != bi

代码结果

运行时间: 88 ms, 内存: 39.2 MB


/*
思路:
1. 使用并查集(Union-Find)来处理连通性问题。
2. 首先初始化并查集。
3. 遍历所有的数对(x, y),如果它们的公因数 z > threshold,则将它们合并。
4. 对于每个查询,判断两个城市是否在同一个集合中。
使用Java Stream API进行处理。
*/

import java.util.stream.IntStream;

public class Solution {
    class UnionFind {
        private int[] parent;
        private int[] rank;

        public UnionFind(int n) {
            parent = new int[n + 1];
            rank = new int[n + 1];
            IntStream.range(0, n + 1).forEach(i -> parent[i] = i);
        }

        public int find(int x) {
            if (parent[x] != x) {
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX != rootY) {
                if (rank[rootX] > rank[rootY]) {
                    parent[rootY] = rootX;
                } else if (rank[rootX] < rank[rootY]) {
                    parent[rootX] = rootY;
                } else {
                    parent[rootY] = rootX;
                    rank[rootX]++;
                }
            }
        }
    }

    public boolean[] areConnected(int n, int threshold, int[][] queries) {
        UnionFind uf = new UnionFind(n);

        IntStream.range(threshold + 1, n + 1).forEach(i -> {
            IntStream.iterate(2 * i, j -> j <= n, j -> j + i).forEach(j -> uf.union(i, j));
        });

        return IntStream.range(0, queries.length)
            .mapToObj(i -> uf.find(queries[i][0]) == uf.find(queries[i][1]))
            .mapToBoolean(Boolean::booleanValue)
            .toArray();
    }
}

解释

方法:

该题解采用了并查集的思想来判断城市之间的连通性。首先,特判当阈值为0时,任意两个自然数都有公约数1,因此所有城市都连通。然后,使用并查集的方法,将每个城市初始化为独立的集合。接着,从阈值+1开始遍历到n,对于每个数p,遍历其所有大于阈值的倍数,并将这些倍数与p进行合并,表示它们之间是连通的。最后,对于每个查询,判断两个城市是否属于同一个集合,即可得到它们是否连通。

时间复杂度:

O(nlogn + mlogn)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中提到,如果阈值为0,则任何两个城市都连通。这是基于什么数学原理?
当阈值为0时,任意两个自然数至少有1作为公共因数,因此任意两个城市之间都可以认为是连通的。在数学上,所有非零整数都至少有1这个共同的因数,所以不论怎样选择两个城市,它们都至少通过1这个公共因数连接起来,符合题目中的连通性定义。
🦆
题解中提到从阈值+1开始遍历到n,为什么选择这个范围?在这个范围外的数有什么不同?
从阈值+1到n之间的数被选择是因为,如果我们考虑阈值以下的数(包括阈值本身),这些数的任何倍数都会小于或等于阈值,因此不会成为两个大于阈值的数的公共因子。因此,只有当我们从阈值+1开始考虑时,我们才开始处理能够成为大于阈值数的有效公共因子。这样可以避免处理无效的连接(即那些不满足阈值要求的连接)。
🦆
题解使用并查集处理连通性问题,但是如何确保初始化每个城市为独立集合后的处理是正确的?
并查集通过维持每个元素的父指针来管理不同集合的归属关系。初始化时,每个城市指向自己,表示它们各自是一个独立的集合。在处理过程中,通过合并操作(union),如果两个城市通过某个公共因子连通,则这两个城市或其相关连的城市会被合并到同一个集合中。这种方法确保了只要有连通路径存在,相关城市最终都会属于同一个集合,从而正确处理连通性问题。
🦆
题解中并查集的合并操作是如何确保所有必要的城市都被合并的?是否可能遗漏某些应该被合并的城市?
并查集的合并操作通过遍历每个大于阈值的数的倍数来实施。对于每个数p,它的所有倍数(大于阈值且小于等于n的)都会与p合并,这确保了所有因共同因子(倍数关系)而连通的城市都被合并到同一集合。虽然这个方法有效地覆盖了大多数场景,但理论上如果处理不当(如遍历不完全或阈值处理错误),可能会遗漏一些连接。然而,按照题解的逻辑,只要正确实施并查集的操作并完全遍历,就应该能够确保所有必要的连通性都被正确处理。

相关问题