grandyang / leetcode

Provide all my solutions and explanations in Chinese for all the Leetcode coding problems.
6.16k stars 737 forks source link

[LeetCode] 685. Redundant Connection II #685

Open grandyang opened 5 years ago

grandyang commented 5 years ago

 

In this problem, a rooted tree is a directed graph such that, there is exactly one node (the root) for which all other nodes are descendants of this node, plus every node has exactly one parent, except for the root node which has no parents.

The given input is a directed graph that started as a rooted tree with N nodes (with distinct values 1, 2, ..., N), with one additional directed 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] that represents a directed edge connecting nodes u and v, where u is a parent of child v.

Return an edge that can be removed so that the resulting graph is a rooted tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array.

Example 1:

Input: [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation: The given directed graph will be like this:
  1
 / \
v   v
2-->3

 

Example 2:

Input: [[1,2], [2,3], [3,4], [4,1], [1,5]]
Output: [4,1]
Explanation: The given directed graph will be like this:
5 <- 1 -> 2
     ^    |
     |    v
     4 <- 3

 

Note:

 

这道题是之前那道 Redundant Connection 的拓展,那道题给的是无向图,只需要删掉组成环的最后一条边即可,归根到底就是检测环就行了。而这道题给的是有向图,整个就复杂多了,因为有多种情况存在,比如给的例子1就是无环,但是有入度为2的结点3。再比如例子2就是有环,但是没有入度为2的结点。其实还有一种情况例子没有给出,就是既有环,又有入度为2的结点。好,现在就来总结一下这三种情况:

第一种:无环,但是有结点入度为2的结点(结点3)

[[1,2], [1,3], [2,3]]

 

  1
 / \
v   v
2-->3

 

第二种:有环,没有入度为2的结点

[[1,2], [2,3], [3,4], [4,1], [1,5]]

 

5 <- 1 -> 2
     ^    |
     |    v
     4 <- 3

 

第三种:有环,且有入度为2的结点(结点1)

[[1,2], [2,3], [3,1], [4,1]]

 

     4
    /
   v
   1
 /  ^
v    \
2 -->3

 

对于这三种情况的处理方法各不相同,首先对于第一种情况,返回的是产生入度为2的后加入的那条边 [2, 3],而对于第二种情况,返回的是刚好组成环的最后加入的那条边 [4, 1],最后对于第三种情况返回的是组成环,且组成入度为2的那条边 [3, 1]。

明白了这些,先来找入度为2的点,如果有的话,那么将当前产生入度为2的后加入的那条边标记为 second,前一条边标记为 first。注意这里找到入度为2的结点后,要将 edge[1] 赋值为0,这样相当于断开了最后产生入度为2的结点的那条边。为什么要这么做呢,这是为了避免出现由于顺序不同而返回错误的结果,比如上面的第三种情况,此时的 first 是 [3,1],second 是 [4,1],最终能找到环,所以返回了 first,是没问题的。但是假如给定的顺序是 [[1,2], [2,3], [4,1], [3,1]],则此时的 first 是 [4,1],second 是 [3,1],最终还是能找到环,但返回的 first 就是错误的结果,所以此时断开了 [3,1] 这条边,就不会有环,则最终回返回 second,是正确答案。为了方便起见,找环使用联合查找 Union Find 的方法,可参见 Redundant Connection 中的解法三。当找到了环之后,如果 first 不存在,说明是第二种情况,返回刚好组成环的最后加入的那条边。如果 first 存在,说明是第三种情况,返回 first。如果没有环存在,说明是第一种情况,返回 second,参见代码如下:

 

class Solution {
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int n = edges.size();
        vector<int> root(n + 1), first, second;
        for (auto& edge : edges) {
            if (root[edge[1]] == 0) {
                root[edge[1]] = edge[0];
            } else {
                first = {root[edge[1]], edge[1]};
                second = edge;
                edge[1] = 0;
            }
        }
        for (int i = 0; i <= n; ++i) root[i] = i;
        for (auto& edge : edges) {
            if (edge[1] == 0) continue;
            int x = getRoot(root, edge[0]), y = getRoot(root, edge[1]);
            if (x == y) return first.empty() ? edge : first;
            root[x] = y;
        }
        return second;
    }
    int getRoot(vector<int>& root, int i) {
        return i == root[i] ? i : getRoot(root, root[i]);
    }
};

 

讨论:使用联合查找 Union Find 的方法一般都需要写个子函数,来查找祖宗结点,上面的解法 getRoot() 函数就是这个子函数,使用递归的形式来写的,其实还可以用迭代的方式来写,下面这两种写法都可以:

 

int getRoot(vector<int>& root, int i) {
    while (i != root[i]) {
        root[i] = root[root[i]];
        i = root[i];
    }
    return i;
}

 

int getRoot(vector<int>& root, int i) {
    while (i != root[i]) i = root[i];
    return i;
}

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/685

 

类似题目:

Redundant Connection

Friend Circles

Accounts Merge

Number of Islands II

Graph Valid Tree

Number of Connected Components in an Undirected Graph

Similar String Groups

 

参考资料:

https://leetcode.com/problems/redundant-connection-ii/

https://leetcode.com/problems/redundant-connection-ii/discuss/108045/C++Java-Union-Find-with-explanation-O(n)

https://leetcode.com/problems/redundant-connection-ii/discuss/108058/one-pass-disjoint-set-solution-with-explain

 

LeetCode All in One 题目讲解汇总(持续更新中...)

lld2006 commented 3 years ago

并查集里为了优化查找root的速度, 是不是应该把递归path上的所有node的root都顺便设置了? 这样以后查找就快了。

grandyang commented 3 years ago

并查集里为了优化查找root的速度, 是不是应该把递归path上的所有node的root都顺便设置了? 这样以后查找就快了。

应该是会快一点