1. 程式人生 > >Leetcode 684. Redundant Connection

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]

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.

解題思路

注意到,這個圖中有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;
    }
};