1. 程式人生 > 其它 >leetcode 685. Redundant Connection II(多餘的連線之二)

leetcode 685. Redundant Connection II(多餘的連線之二)

技術標籤:leetcode演算法leetcode

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 from 1 to 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 [ui, vi] that represents a directed edge connecting nodes ui and vi, where ui is a parent of child vi.

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: edges = [[1,2],[1,3],[2,3]]
Output: [2,3]

684題類似,只不過684是無向圖,而685是有向圖。
有向圖的樹正常情況下每個節點應該只有一個parent(除了root以外),但是加了一條邊,讓找出多餘的邊使有向圖變成樹。
邊[u,v]中,u是v的parent。

思路:
比684題多了一種情況,就是有向圖中兩個parent的情況。
正常情況下每個節點只有一個parent,而多加的一條邊可能使某個節點有了兩個parent。分以下兩種

case 1:節點有兩個parent,但是無閉環,如example1
可通過遍歷邊,儲存每個節點的parent,如果發現某個點v之前已經出現過parent,就說明v有兩個parent。如example1中的[1,3], [2,3],遍歷到[2,3]時發現3已經有了parent:1。

這時要麼需要刪除之前出現的那條邊[1,3],要麼需要刪除當前的邊[2,3],先把這兩條邊作為候選分別儲存到res1和res2。

case 2:節點有兩個parent,其中一條邊在閉環中
例如:(圖片來自參考資料
在這裡插入圖片描述
邊上的數字代表邊遍歷的順序,首先找到了節點1有兩個parent,所以儲存1號邊和2號邊,1號邊先出現,儲存到res1。2號邊後出現,儲存在res2。

然後找閉環,如果按684題的做法,有閉環時返回的應該是4號邊,因為它是造成閉環出現的邊(最後出現),但是這裡要優先返回有兩個parent的邊。所以要先看res1中是否有邊,有就返回res1中的邊。
2號邊因為是有兩個parent的邊中後出現的,滿足了題目要求的返回後出現邊的條件,如果沒有閉環就直接返回res2就行了。所以這一步不需要再check。

剩下的情況就是沒有兩個parent,只有閉環,這就是684題了,還是union-find,上面的case2找閉環也在此步驟
只是有一trick,因為case1中儲存進res2的邊在case2中不需要再check,可在check case1時把res2中出現的邊換成[-1, -1] ,後面發現邊是負的可直接跳過。

而且注意case1中儲存的parent,要在後面check閉環前清空。因為閉環又是從頭開始check的。

    public int[] findRedundantDirectedConnection(int[][] edges) {
        int m = edges.length;
        int[] parent = new int[m+1];
        int[] size = new int[m+1];
        int[] res1 = new int[2];
        int[] res2 = new int[2];
        
        Arrays.fill(size, 1);
        
        for(int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            
            if(parent[v] > 0) {
                //共同parent的邊中先出現的
                res1[0] = parent[v]; res1[1] = v;
                //共同parent的邊中後出現的
                res2[0] = u;  res2[1] = v;
                //刪除後出現的邊,後面判斷前面出現的邊是否有閉環,優先返回有閉環的邊
                edge[0] = -1;
                edge[1] = -1;
            }
            parent[v] = u;
        }
        
        Arrays.fill(parent, 0);
        //看剩下的邊是否存在閉環,union-find
        for(int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            if(u < 0) continue;
            
            if(parent[u] == 0) parent[u] = u;
            if(parent[v] == 0) parent[v] = v;
            
            int pu = findRoot(u, parent);
            int pv = findRoot(v, parent);
            
            if(pu == pv){
                //優先返回有兩個parent的邊,如果沒有,返回造成閉環的第一條邊
                return (res1[0] > 0 ? res1 : edge);
            };
            
            if(size[pv] > size[pu]) {
                //swap
                int tmp = pu;
                pu = pv;
                pv = tmp;
            }
            
            parent[pv] = pu;
            size[pu] += size[pv];
        }
        
        //沒有閉環,就返回有兩個parent的邊
        return res2;
    }
    
    int findRoot(int node, int[] parent) {
        while(parent[node] != node) {
            parent[node] = parent[parent[node]];
            node = parent[node];
        }
        return node;
    }