LeetCode题解系列--685. Redundant Connection II

描述

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:
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.

难度:hard

思路

这道题虽然叫做Redundant Connection II 但是和684那道题关系并不是很大,所用的算法也大不相同。
这道题我可以说是走了弯路了。

错误的思路

一开始我的思路是,通过DFS遍历整张图,然后通过先序和后序遍历的结果,判断出每条边的类型,树边、前向边、回边、跨边(翻译不一定准确),那么图将所有的树边取出来就是一颗树,其他三种类型的边都是冗余的。对于具体的边的类型的判断,课本上有相关的资料,网上也有很多代码,这里就不多说明。

这种算法其实去掉的那条边是正确的,也就是去掉那条边后确实是得到了一颗合法的树,但是题目有这样一条要求“return the answer that occurs last in the given 2D-array.”,而我的这个算法是不能满足这个要求的。我添加了多个判断条件都是做不到题目这点要求。不过还是将我的不符合要求的代码放出来:

#include 
#include 
using namespace std;
class Solution {
private:
    int clock;
    vector<bool> visited;
    vector<int> pre;
    vector<int> post;
    vector< vector<int> > graph;
    vector<int> inGrade;
    vector<int> outGrade;
    int N;
    void explore(int v) {
        cout << "explore : " << v << endl;
        visited[v] = true;
        previsit(v);
        for (int i = 1; i <= N; ++i) {
            // for all edges from v
          // 用于区分树边和前向边,因为对于这两种边根据先序和后序遍历的顺序无法区分
          // 将树边标记为2
            if (1 == graph[v][i] && false == visited[i]) {
                // means this edge is in DFS tree
                graph[v][i] = 2;
                explore(i);
            }
        }
        postvisit(v);
    }
    void previsit(int v) {
        pre[v] = clock;
        ++clock;
    }
    void postvisit(int v) {
        post[v] = clock;
        ++clock;
    }
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        // first transfer the graph into Adjancent Matrix
        // from 1 to N
        N = edges.size();
        inGrade = vector<int>(N + 1, 0);
        outGrade = vector<int>(N + 1, 0);
        graph = vector< vector<int> >(N + 1,vector<int>(N + 1, 0));
        for (int i = 0; i < edges.size(); ++i) {
            graph[edges[i][0]][edges[i][1]] = 1;
            ++inGrade[edges[i][1]];
            ++outGrade[edges[i][0]];
        }

        // variable for DFS
        visited = vector<bool>(N + 1, false);
        pre     = vector<int>(N + 1, 0);
        post    = vector<int>(N + 1, 0);
        clock = 1;

        for (int i = 1; i <= N; ++i) {
            if (false == visited[i]) {
                this->explore(i);                
            }
        }

        for (int i = N - 1; i >= 0; --i) {
            int u = edges[i][0];
            int v = edges[i][1];
            // cross edges
            if ((pre[v] < post[v] && post[v] < pre[u] && pre[u] < post[u]) || (inGrade[v] > 1)) {
                cout << "Find Cross Edges" << endl;
                if (1 == outGrade[u] && 0 == inGrade[u]) {
                    cout << "pass this Cross Edge" << endl;
                    continue;
                }
                if (2 == graph[u][v]) {
                    cout << "pass this Cross Edge" << endl;
                    continue;
                }
                // cout << "Find Cross Edges" << endl;
                return edges[i];
            // Back Edges
            } else if (pre[v] < pre[u] && pre[u] < post[u] && post[u] < post[v]) {
                cout << "Find Back Edges" << endl;
                return edges[i];
            // Forward Edges
            } else if (pre[u] < pre[v] && pre[v] < post[v] && post[v] < post[u]) {
                // Means this is not a tree edges
                // When DFS the graph, those tree edges are labled 2
                if (1 == graph[u][v]) {
                    cout << "Find Forward Edges" << endl;
                    return edges[i];
                }
            }
        }
    }
};

正确的思路

感谢@niwota

其实对于冗余的边只有以下三种可能:

  • 无环的情况

    1
    / \
    v v
    2–>3

  • 有环情况一

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

  • 有环情况二

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

  • 对于无环的情况,只需找到入度为2的点,选择比较后出现的那条边返回即可。
  • 对于有环情况一,需要删除环内任一条边,找出最后出现的那条边即可。
  • 对于有环情况二,找到入度为2的点,删除在环内的那条边。([4,1])

综上,这个算法就有两步:

  • 第一步,找到入度为2的点(当然,有可能不存在)
  • 第二步,搜索图中是否存在环(DFS即可)

关于一些细节问题,在代码中可以体现

#include 
#include 
#include 
using namespace std;

class Solution {
private:
    vector<int> status;
    vector<vector<int>> graph;
    vector<int> inGrade;
    vector<int> outGrade;
    stack<int> loop, temp_loop;
    int N;
    int nodeHaveTwoParent;
    bool whetherHaveLoop;
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        N = edges.size();
        whetherHaveLoop = false;
        nodeHaveTwoParent = -1;
        inGrade = vector<int>(N + 1, 0);
        outGrade = vector<int>(N + 1, 0);
        graph = vector< vector<int> >(N + 1,vector<int>(N + 1, -1));
        status = vector<int>(N + 1, 0);
        for (int i = 0; i < edges.size(); ++i) {
            // store the graph and store the order of edges at the mean time.
            graph[edges[i][0]][edges[i][1]] = i;
            ++inGrade[edges[i][1]];
            ++outGrade[edges[i][0]];
            if (2 == inGrade[edges[i][1]]) {
                // find node have two parent
                nodeHaveTwoParent = edges[i][1];
            }
        }
        for (int i = 1; i <= N; ++i) {
            if (true == whetherHaveLoop) {
                break;
            } else if (0 == status[i]) {
                temp_loop = stack<int>();
                temp_loop.push(i);
                status[i] = 1;
                this->DFS(i);
                status[i] = 2;
            }
        }
        // case 1
        if (false == whetherHaveLoop) {
            cout << "case one";
            int order = -1;
            for (int i = 1; i <= N; ++i) {
                if (graph[i][nodeHaveTwoParent] > order) {
                    order = graph[i][nodeHaveTwoParent];
                }
            }
            return edges[order];
        } else {
            int latestOccur = -1;
            while (!loop.empty()) {
                int child = loop.top();
                loop.pop();
                if (loop.empty()) {
                    break;
                }
                int parent = loop.top();
                cout << "path : " << parent << " " << child << endl;
                // case 2
                if (nodeHaveTwoParent != -1 && child == nodeHaveTwoParent) {
                    cout << "case 2" << endl;
                    return edges[graph[parent][child]];
                }
                if (graph[parent][child] > latestOccur) {
                    latestOccur = graph[parent][child];
                    cout << "case 3 : " << latestOccur << endl;                    
                }
            }
            return edges[latestOccur];
        }
    }

    void DFS(int start) {
        cout << "DFS : " << start << endl;
        for (int i = 1; i <= N; ++i) {
            if (graph[start][i] != -1) {
                // find loop
                if (1 == status[i]) {
                    temp_loop.push(i);
                    whetherHaveLoop = true;
                    loop = temp_loop;
                    return;
                } else if (0 == status[i]) {
                    temp_loop.push(i);
                    status[i] = 1;
                    this->DFS(i);
                    status[i] = 2;
                    temp_loop.pop();
                }
            }
        }
    }
};

关于DFS判断是否有环有很多种方法,也很容易理解,不做过多的解释。不过我的实现仍旧有100行,另外我偷懒使用了邻接矩阵的方式存图,其实对于这种相对稀疏的图应该使用邻接链表的方式。

另外在discussion中还有人提供了一种采用并查集的算法,编码更加简洁,他基本也是判断我上面所提到的三种情况,但是我看了许久,也没有看懂他的代码。

给出参考:

class Solution {
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int n = edges.size();
        vector<int> parent(n+1, 0), candA, candB;
        // step 1, check whether there is a node with two parents
        for (auto &edge:edges) {
            if (parent[edge[1]] == 0)
                parent[edge[1]] = edge[0]; 
            else {
                candA = {parent[edge[1]], edge[1]};
                candB = edge;
                edge[1] = 0;
            }
        } 
        // step 2, union find
        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], pu = root(parent, u);
            // Now every node only has 1 parent, so root of v is implicitly v
            if (pu == v) {
                if (candA.empty()) return edge;
                return candA;
            }
            parent[v] = pu;
        }
        return candB;
    }
private:
    int root(vector<int>& parent, int k) {
        if (parent[k] != k) 
            parent[k] = root(parent, parent[k]);
        return parent[k];
    }
};

点击这里查看更多我的leetcode答案

你可能感兴趣的:(C++,leetcode)