684. Redundant Connection
题目
In this problem, a tree is an undirected graph that is connected and has no cycles.
The given input is a graph that started as a tree with N nodes (with distinct values 1, 2, …, N), with one additional edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.
The resulting graph is given as a 2D-array of edges
. Each element of edges
is a pair [u, v]
with u < v
, that represents an undirected edge connecting nodes u
and v
.
Return an edge that can be removed so that the resulting graph is a tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array. The answer edge [u, v]
should be in the same format, with u < v
.
Example 1:
Input: [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation: The given undirected graph will be like this:
1
/ \
2 - 3
Example 2:
Input: [[1,2], [2,3], [3,4], [1,4], [1,5]]
Output: [1,4]
Explanation: The given undirected graph will be like this:
5 - 1 - 2
| |
4 - 3
Note:
The size of the input 2D-array will be between 3 and 1000.
Every integer represented in the 2D-array will be between 1 and N, where N is the size of the input array.
解题思路
注意到,这个图中有N个节点和N条不相同的边,因此只多了一条边。可以采取逐个向图中添加边的方式,当添加一条边形成环时,去掉这个环中任意一条边都能形成树。而题目要求我们返回最后给出的边,因此,当添加一条边将形成环时,这条边就是我们要的答案。
方法一:DFS
创建一个空图,逐渐添加边。在每次添加边前,通过dfs检查这条边的2个节点是否已经连通。如果已连通,那么已连通的路径加上要添加的边将行成环,因此这条边就是最后多余的边;如果未连通,则将这条边添加进图中。
实现细节:可以采用邻接表的形式储存图,在dfs时,添加一个pre参数,使得节点跳过父节点,只遍历子节点。
代码如下:
class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
unordered_map<int, unordered_set<int>> graph;
for (auto edge : edges) {
if (dfs(graph, edge[0], edge[1], 0)) return edge;
graph[edge[0]].insert(edge[1]);
graph[edge[1]].insert(edge[0]);
}
return {};
}
bool dfs(unordered_map<int, unordered_set<int>> &graph, int src, int dst, int pre) {
if (graph[src].count(dst)) return true;
for (int nei : graph[src]) {
if (nei != pre) {
if (dfs(graph, nei, dst, src)) return true;
}
}
return false;
}
};
方法二:Union-Find(quick-union)
与方法一一样,逐渐向图中添加边,直到要添加的边将形成环。不同的是,添加边不是直接添加这两个节点连成的边,而是连接这两个节点所属的树的根节点,并将其中一个根节点作为新树的根节点(直接添加边和添加这两个节点的根节点的边效果相同,都是形成一棵新树,即连通的无环图)。我们为每个节点维护一个pre先前节点,先前节点为0的节点是根节点。初始时,有N个只有根节点的树,添加边就是该边2个节点的根节点相互连接,形成新的树,而节点的根节点可以通过pre先前节点逐渐找到。如果要添加的边的2个节点的根节点相同,说明这两个节点已经连通,因此这条边就是多余的边。
代码如下:
class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
vector<int> pre(2001, 0);
for (auto edge : edges) {
int rootx = find(pre, edge[0]);
int rooty = find(pre, edge[1]);
if (rootx == rooty) return edge;
pre[rootx] = rooty;
}
return {};
}
int find(vector<int>& pre, int i) {
while (pre[i] != 0) {
i = pre[i];
}
return i;
}
};
方法三:Union-Find(Weighted Union-Find)
方法二将两棵树根节点连接时,总是取第一棵树的根节点作为新的根节点,可能会导致树不平衡而增大找到节点的根节点的时间。因此可以给每个节点维护一个size,记录以该节点作为根节点的树的节点数目。连接两棵树时,选取size大的树的根节点作为新的根节点。
代码如下:
class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
vector<int> pre(2001, 0);
vector<int> size(2001, 1);
for (auto edge : edges) {
int rootx = find(pre, edge[0]);
int rooty = find(pre, edge[1]);
if (rootx == rooty) return edge;
if (size[rootx] >= size[rooty]){
pre[rooty] = rootx;
size[rootx] += size[rooty];
} else {
pre[rootx] = rooty;
size[rooty] += size[rootx];
}
}
return {};
}
int find(vector<int>& pre, int i) {
while (pre[i] != 0) {
i = pre[i];
}
return i;
}
};