Hard
题目描述
在这个问题中,有根树是一个有向图,满足:有且仅有一个节点(根节点)没有父节点,其他所有节点都有且仅有一个父节点。
给定一个有 n 个节点(节点值为 1 到 n)的有向图,该图原本是一棵有根树,但添加了一条额外的有向边。添加的这条边连接了两个不同的节点,且这条边不是原图中已存在的边。
结果图以二维数组 edges 的形式给出,其中 edges[i] = [ui, vi] 表示存在一条从 ui 指向 vi 的有向边,ui 是 vi 的父节点。
返回一条可以删除的边,使得结果图是一个有 n 个节点的有根树。如果有多个答案,返回在给定二维数组中最后出现的边。
示例 1:
输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]
示例 2:
输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]
提示:
- n == edges.length
- 3 <= n <= 1000
- edges[i].length == 2
- 1 <= ui, vi <= n
- ui != vi
解题思路
这道题的关键是理解有根树的特性:每个节点最多有一个父节点,且整个图不能有环。
添加一条边后,可能出现两种情况:
- 某个节点有两个父节点:违反了有根树的定义
- 图中出现环:违反了树的定义
解题思路:
- 检测双父节点:遍历所有边,记录每个节点的入度。如果某个节点入度为2,说明有两个父节点
- 检测环:使用并查集来检测环的存在
具体步骤:
- 先找出所有可能导致双父节点的边(candidate1, candidate2)
- 如果没有双父节点问题,直接用并查集找第一个形成环的边
- 如果有双父节点问题:
- 先尝试删除后出现的边(candidate2),看剩余图是否有环
- 如果仍有环,则删除先出现的边(candidate1)
这种方法确保了我们总是返回最后出现的可删除边。
时间复杂度为 O(n),空间复杂度为 O(n)。
代码实现
class Solution {
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int n = edges.size();
vector<int> parent(n + 1);
vector<int> candidate1, candidate2;
// 检查是否有节点有两个父节点
for (auto& edge : edges) {
int u = edge[0], v = edge[1];
if (parent[v] == 0) {
parent[v] = u;
} else {
candidate1 = {parent[v], v};
candidate2 = {u, v};
edge[1] = 0; // 暂时移除第二个候选边
}
}
// 并查集检查环
for (int i = 1; i <= n; i++) parent[i] = i;
for (auto& edge : edges) {
if (edge[1] == 0) continue; // 跳过被移除的边
int u = edge[0], v = edge[1];
int pu = find(parent, u), pv = find(parent, v);
if (pu == pv) {
// 发现环
return candidate1.empty() ? edge : candidate1;
}
parent[pv] = pu;
}
return candidate2;
}
private:
int find(vector<int>& parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]);
}
return parent[x];
}
};
class Solution:
def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
n = len(edges)
parent = [0] * (n + 1)
candidate1 = candidate2 = None
# 检查是否有节点有两个父节点
for i, (u, v) in enumerate(edges):
if parent[v] == 0:
parent[v] = u
else:
candidate1 = [parent[v], v]
candidate2 = [u, v]
edges[i][1] = 0 # 暂时移除第二个候选边
# 并查集检查环
parent = list(range(n + 1))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
for u, v in edges:
if v == 0: # 跳过被移除的边
continue
pu, pv = find(u), find(v)
if pu == pv:
# 发现环
return candidate1 if candidate1 else [u, v]
parent[pv] = pu
return candidate2
public class Solution {
public int[] FindRedundantDirectedConnection(int[][] edges) {
int n = edges.Length;
int[] parent = new int[n + 1];
int[] candidate1 = null, candidate2 = null;
// 检查是否有节点有两个父节点
for (int i = 0; i < edges.Length; i++) {
int u = edges[i][0], v = edges[i][1];
if (parent[v] == 0) {
parent[v] = u;
} else {
candidate1 = new int[] {parent[v], v};
candidate2 = new int[] {u, v};
edges[i][1] = 0; // 暂时移除第二个候选边
}
}
// 并查集检查环
for (int i = 1; i <= n; i++) parent[i] = i;
foreach (var edge in edges) {
if (edge[1] == 0) continue; // 跳过被移除的边
int u = edge[0], v = edge[1];
int pu = Find(parent, u), pv = Find(parent, v);
if (pu == pv) {
// 发现环
return candidate1 ?? edge;
}
parent[pv] = pu;
}
return candidate2;
}
private int Find(int[] parent, int x) {
if (parent[x] != x) {
parent[x] = Find(parent, parent[x]);
}
return parent[x];
}
}
var findRedundantDirectedConnection = function(edges) {
const n = edges.length;
let parent = new Array(n + 1).fill(0);
let candidate1 = null, candidate2 = null;
// 检查是否有节点有两个父节点
for (let i = 0; i < edges.length; i++) {
const [u, v] = edges[i];
if (parent[v]
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n·α(n)),其中 α 是反阿克曼函数,在实际应用中可视为常数 |
| 空间复杂度 | O(n),用于存储并查集的父节点数组 |
相关题目
- . Redundant Connection (Medium)