Skip to content

Latest commit

 

History

History
310 lines (199 loc) · 8.44 KB

0684._Redundant_Connection.md

File metadata and controls

310 lines (199 loc) · 8.44 KB

684. Redundant Connection

难度: Medium

刷题内容

原题连接

内容描述

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 directed graph follow up please see Redundant Connection II). We apologize for any inconvenience caused.

解题方案

思路 1 - 时间复杂度: O(N * V * E)- 空间复杂度: O(N * V)******

union-find

稍微注意一下我的union-find boilerplate里面的uf是0-based,而这里的点集是1-based

所以要对edges做预处理 edges = [[i-1,j-1] for i, j in edges]

具体思想就是去掉一条边后看是否为树,即无环且full connected

因为用union-find我们是通过判断所有访问的点是否都是同一个parent来看是否full connected,所以full connected是肯定保证了的

beats 14.55%

class Graph:
    def __init__(self, n, edges):
        self.edges = edges
        self.n = n
        self.uf = [i for i in range(n)]

    def find(self, x, uf):
        while x != uf[x]:
            uf[x] = uf[uf[x]]
            x = uf[x]
        return uf[x]

    def union(self, x, y, uf):
        x_root = self.find(x, uf)
        y_root = self.find(y, uf)
        uf[x_root] = y_root

    def hasCycle(self):
        for node1, node2 in self.edges:
            if self.find(node1, self.uf) == self.find(node2, self.uf):  # cycle exists
                return True
            else:
                self.union(node1, node2, self.uf)
        return False

    def fullyConnected(self):
        self.hasCycle()  # 必须先调用一下这段代码实现preprocess
        return len(set(self.find(i, self.uf) for i in self.uf)) == 1  # fully connected
        
class Solution:
    def findRedundantConnection(self, edges):
        """
        :type edges: List[List[int]]
        :rtype: List[int]
        """
        edges = [[i-1,j-1] for i, j in edges]
        n = len(set([i for i,j in edges] + [j for i,j in edges]))
        for i in range(len(edges)-1, -1, -1):
            graph = Graph(n, edges[:i]+edges[i+1:])
            if not graph.hasCycle():
                x, y = edges[i]
                return [x+1, y+1]

思路 2 - 时间复杂度: O(N * (V + E))- 空间复杂度: O(N * V)******

DFS, 具体思想就是去掉一条边后看是否为树,即无环且full connected

因为用DFS我们是通过判断是否所有的点我们都访问到了来看是否full connected,所以full connected是无法保证

用DFS我们是以某一个点为入口往下进行深度遍历,但是如果我们用来作为入口的这个点正好被我们删除了呢?即,如果我们删除掉这条边的话, 这个点就与图中所有的点都隔离开了呢?

例如输入为

10
[[2,7],[7,8],[3,6],[2,5],[6,8],[4,8],[2,8],[1,8],[7,10],[3,9]]

删除边[1,8],这样还会有环,2 -> 7 -> 8 -> 2,假如我们像union-find一样只判断if not graph.hasCycle()的话,答案是错的

因为我们从入口1(即原始input中的点0)出发,我们一个点都到不了,程序自然说没有环了,如果我们傻傻地返回[1,8]为答案就错了

因此正确的做法是要先确定删除这条边以后我们整个图还是full connected的,即从入口还是可以一直访问下去的,而且还要保证删除这条边之后我们没有环了

beats 14.55%

class Graph(object):
    def __init__(self, vertex_cnt, edges):
        self.vertex_cnt = vertex_cnt
        self.graph = collections.defaultdict(list)
        self.visited = [False] * self.vertex_cnt
        for node1, node2 in edges:
            self.addEdge(node1, node2)

    def addEdge(self, v1, v2):
        self.graph[v1].append(v2)
        self.graph[v2].append(v1)

    def hasCycleHelper(self, v, visited, prev_v):
        self.visited[v] = True
        for i in self.graph[v]:
            if not visited[i]:
                if self.hasCycleHelper(i, visited, v):
                    return True
            else:
                if i != prev_v:
                    return True
        return False

    def hasCycle(self):
        if self.hasCycleHelper(0, self.visited, -1):
            return True
        return False

    def fullyConnected(self):
        return all(self.visited)
    
class Solution:
    def findRedundantConnection(self, edges):
        """
        :type edges: List[List[int]]
        :rtype: List[int]
        """
        edges = [[i-1,j-1] for i, j in edges]
        n = len(set([i for i,j in edges] + [j for i,j in edges]))
        for i in range(len(edges)-1, -1, -1):
            graph = Graph(n, edges[:i]+edges[i+1:])
            if not graph.hasCycle() and graph.fullyConnected():
                x, y = edges[i]
                return [x+1, y+1]

思路 3 - 时间复杂度: O(N * E)- 空间复杂度: O(N)******

但是其实我们这样想,如果我们从edges逆序开始遍历,不断地组成一个图,当碰到一条边(u, v)的时候,如果这个时候我们其实已经可以从u走到v了,说明在碰到这条边之前u和v就已经是联通的,那么说明当前这条边是duplicate

union_find, beats 54.83%

class UnionFind(object):
    def uf(self, n):  # 初始化uf数组和组数目
        self.count = n
        self.uf = [i for i in range(n)]
        self.size = [1] * n # 每个联通分量的size

    def find(self, x):  # 判断节点所属于的组
        while x != self.uf[x]:
            self.uf[x] = self.uf[self.uf[x]]
            x = self.uf[x]
        return self.uf[x]

    def union(self, x, y):  # 连接两个节点
        x_root = self.find(x)
        y_root = self.find(y)
        if x_root != y_root:
            self.size[y_root] += self.size[x_root]
        self.uf[x_root] = y_root
        self.count -= 1

    def connected(self, x, y):  # 判断两个节点是否联通
        return self.find(x) == self.find(y)
        
class Solution:
    def findRedundantConnection(self, edges):
        """
        :type edges: List[List[int]]
        :rtype: List[int]
        """
        edges = [[i-1,j-1] for i, j in edges]
        n = len(set([i for i,j in edges] + [j for i,j in edges]))
        unionfind = UnionFind()
        unionfind.uf(n)
        for node1, node2 in edges:
            if not unionfind.connected(node1, node2):
                unionfind.union(node1, node2)
            else:
                return [node1+1, node2+1]

思路 4 - 时间复杂度: O(N * E)- 空间复杂度: O(N)******

同样的道理,我们可以用DFS来实现, beats 28.39%

class Solution:
    def findRedundantConnection(self, edges):
        """
        :type edges: List[List[int]]
        :rtype: List[int]
        """
        graph = collections.defaultdict(list)

        def dfs(source, target):
            if source not in visited:
                visited.add(source)
                if source == target: 
                    return True
                return any(dfs(nei, target) for nei in graph[source])

        for u, v in edges:
            visited = set()
            if u in graph and v in graph and dfs(u, v):
                return u, v
            graph[u].append(v)
            graph[v].append(u)