Hard
题目描述
我们有 n 个城市,标号为 1 到 n。当且仅当城市 x 和 y 有一个大于某个阈值的公共因子时,标号为 x 和 y 的两个不同城市之间才会有一条双向道路直接连接。
更正式地说,如果存在整数 z 满足以下所有条件,那么标号为 x 和 y 的城市之间就有一条道路:
- x % z == 0
- y % z == 0
- z > threshold
给定两个整数 n 和 threshold,以及一个查询数组 queries,你必须判断每个查询 queries[i] = [ai, bi] 中城市 ai 和 bi 是否直接或间接连通(即它们之间存在某条路径)。
返回一个数组 answer,其中 answer.length == queries.length,如果第 i 个查询中 ai 和 bi 之间存在路径,则 answer[i] 为 true,否则为 false。
示例 1:
输入:n = 6, threshold = 2, queries = [[1,4],[2,5],[3,6]]
输出:[false,false,true]
示例 2:
输入:n = 6, threshold = 0, queries = [[4,5],[3,4],[3,2],[2,6],[1,3]]
输出:[true,true,true,true,true]
示例 3:
输入:n = 5, threshold = 1, queries = [[4,5],[4,5],[3,2],[2,3],[3,4]]
输出:[false,false,false,false,false]
约束条件:
- 2 <= n <= 10^4
- 0 <= threshold <= n
- 1 <= queries.length <= 10^5
- queries[i].length == 2
- 1 <= ai, bi <= n
- ai != bi
解题思路
这是一个图的连通性问题,可以使用并查集来解决。
核心思路:
建图过程:两个城市 x 和 y 连通当且仅当它们有大于 threshold 的公共因子。我们需要找到所有这样的城市对并用并查集连接它们。
优化建图:直接枚举所有城市对会超时。关键观察是:如果两个数有公共因子 d > threshold,那么它们都是 d 的倍数。因此我们可以:
- 遍历所有可能的因子 d(从 threshold+1 到 n)
- 对于每个因子 d,找到所有它的倍数:d, 2d, 3d, …
- 将这些倍数连接在同一个连通分量中
并查集操作:
- 对于因子 d 的所有倍数,我们将它们依次连接:连接 d 和 2d,连接 2d 和 3d,以此类推
- 这样所有 d 的倍数就都在同一个连通分量中了
查询处理:对于每个查询 [a, b],只需检查 a 和 b 是否在同一个连通分量中。
时间复杂度分析:
- 建图:O(n log n),因为对每个因子 d,需要处理 n/d 个倍数
- 查询:O(α(n)) 每次查询,其中 α 是阿克曼函数的反函数
代码实现
class Solution {
public:
vector<int> parent;
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
int px = find(x), py = find(y);
if (px != py) {
parent[px] = py;
}
}
vector<bool> areConnected(int n, int threshold, vector<vector<int>>& queries) {
parent.resize(n + 1);
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
// 对于每个大于threshold的因子,连接其所有倍数
for (int d = threshold + 1; d <= n; d++) {
vector<int> multiples;
for (int multiple = d; multiple <= n; multiple += d) {
multiples.push_back(multiple);
}
// 连接所有倍数
for (int i = 1; i < multiples.size(); i++) {
unite(multiples[0], multiples[i]);
}
}
vector<bool> result;
for (auto& query : queries) {
result.push_back(find(query[0]) == find(query[1]));
}
return result;
}
};
class Solution:
def areConnected(self, n: int, threshold: int, queries: List[List[int]]) -> List[bool]:
parent = list(range(n + 1))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def unite(x, y):
px, py = find(x), find(y)
if px != py:
parent[px] = py
# 对于每个大于threshold的因子,连接其所有倍数
for d in range(threshold + 1, n + 1):
multiples = list(range(d, n + 1, d))
# 连接所有倍数
for i in range(1, len(multiples)):
unite(multiples[0], multiples[i])
result = []
for a, b in queries:
result.append(find(a) == find(b))
return result
public class Solution {
private int[] parent;
private int Find(int x) {
if (parent[x] != x) {
parent[x] = Find(parent[x]);
}
return parent[x];
}
private void Unite(int x, int y) {
int px = Find(x), py = Find(y);
if (px != py) {
parent[px] = py;
}
}
public IList<bool> AreConnected(int n, int threshold, int[][] queries) {
parent = new int[n + 1];
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
// 对于每个大于threshold的因子,连接其所有倍数
for (int d = threshold + 1; d <= n; d++) {
List<int> multiples = new List<int>();
for (int multiple = d; multiple <= n; multiple += d) {
multiples.Add(multiple);
}
// 连接所有倍数
for (int i = 1; i < multiples.Count; i++) {
Unite(multiples[0], multiples[i]);
}
}
List<bool> result = new List<bool>();
foreach (var query in queries) {
result.Add(Find(query[0]) == Find(query[1]));
}
return result;
}
}
var areConnected = function(n, threshold, queries) {
const parent = Array.from({length: n + 1}, (_, i) => i);
function find(x) {
if (parent[x] !== x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
function union(x, y) {
const px = find(x);
const py = find(y);
if (px !== py) {
parent[px] = py;
}
}
for (let z = threshold + 1; z <= n; z++) {
for (let multiple = z; multiple <= n; multiple += z) {
if (multiple !== z) {
union(z, multiple);
}
}
}
return queries.map(([a, b]) => find(a) === find(b));
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n + m α(n)) | 建图需要 O(n log n),m 次查询每次 O(α(n)) |
| 空间复杂度 | O(n) | 并查集存储空间 |