1. 程式人生 > >[LeetCode] Redundant Connection 冗餘的連線

[LeetCode] 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.

Update (2017-09-26):
We have overhauled the problem description + test cases and specified clearly the graph is an undirected graph. For the directedgraph follow up please see Redundant Connection II). We apologize for any inconvenience caused.

這道題給我們了一個無向圖,讓我們刪掉組成環的最後一條邊,其實這道題跟之前那道Graph Valid Tree基本沒什麼區別,三種解法都基本相同。博主覺得老題稍微變一下就是一道新題,而onsite遇到原題的概率很小,大多情況下都會稍稍變一下,所以舉一反三的能力真的很重要,要完全吃透一道題也不太容易,需要多下功夫。我們首先來看遞迴的解法,這種解法的思路是,每加入一條邊,就進行環檢測,一旦發現了環,就返回當前邊。對於無向圖,我們還是用鄰接表來儲存,建立每個結點和其所有鄰接點的對映,由於兩個結點之間不算有環,所以我們要避免這種情況 1->{2}, 2->{1}的死迴圈,所以我們用一個變數pre記錄上一次遞迴的結點,比如上一次遍歷的是結點1,那麼在遍歷結點2的鄰接表時,就不會再次進入結點1了,這樣有效的避免了死迴圈,使其能返回正確的結果,參見程式碼如下:

解法一:

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        unordered_map<int, unordered_set<int>> m;
        for (auto edge : edges) {
            if (hasCycle(edge[0], edge[1], m, -1)) return edge;
            m[edge[0]].insert(edge[1]);
            m[edge[1]].insert(edge[0]);
        }
        return {};
    }
    bool hasCycle(int cur, int target, unordered_map<int, unordered_set<int>>& m, int pre) {
        if (m[cur].count(target)) return true;
        for (int num : m[cur]) {
            if (num == pre) continue;
            if (hasCycle(num, target, m, cur)) return true;
        }
        return false;
    }
};

既然遞迴能做,一般來說迭代也木有問題。但是由於BFS的遍歷機制和DFS不同,所以沒法採用利用變數pre來避免上面所說的死迴圈(不是很確定,可能是博主沒想出來,有做出來的請在評論區貼上程式碼),所以我們採用一個集合來記錄遍歷過的結點,如果該結點已經遍歷過了,那麼直接跳過即可,否則我們就把該結點加入queue和集合,繼續迴圈,參見程式碼如下:

解法二:

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        unordered_map<int, unordered_set<int>> m;
        for (auto edge : edges) {
            queue<int> q{{edge[0]}};
            unordered_set<int> s{{edge[0]}};
            while (!q.empty()) {
                auto t = q.front(); q.pop();
                if (m[t].count(edge[1])) return edge;
                for (int num : m[t]) {
                    if (s.count(num)) continue;
                    q.push(num);
                    s.insert(num);
                }
            }
            m[edge[0]].insert(edge[1]);
            m[edge[1]].insert(edge[0]);
        }
        return {};
    }
};

其實這道題最好的解法使用Union Find來做,論壇上清一色的都是用這種解法來做的,像博主用DFS和BFS這麼清新脫俗的方法還真不多:) 其實Union Find的核心思想並不是很難理解,首先我們建立一個長度為(n+1)的陣列root,由於這道題並沒有明確的說明n是多少,只是說了輸入的二位陣列的長度不超過1000,那麼n絕對不會超過2000,我們加1的原因是由於結點值是從1開始的,而陣列是從0開始的,我們懶得轉換了,就多加一位得了。我們將這個陣列都初始化為-1,有些人喜歡初始化為i,都可以。開始表示每個結點都是一個單獨的組,所謂的Union Find就是要讓結點之間建立關聯,比如若root[1] = 2,就表示結點1和結點2是相連的,root[2] = 3表示結點2和結點3是相連的,如果我們此時新加一條邊[1, 3]的話,我們通過root[1]得到2,再通過root[2]得到3,說明結點1有另一條路徑能到結點3,這樣就說明環是存在的;如果沒有這條路徑,那麼我們要將結點1和結點3關聯起來,讓root[1] = 3即可,參見程式碼如下:

解法三:

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        vector<int> root(2001, -1);
        for (auto edge : edges) {
            int x = find(root, edge[0]), y = find(root, edge[1]);
            if (x == y) return edge;
            root[x] = y;
        }
        return {};
    }
    int find(vector<int>& root, int i) {
        while (root[i] != -1) {
            i = root[i];
        }
        return i;
    }
};

類似題目:

參考資料: