Leetcode 684. Redundant Connection
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]
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;
}
};