题目
在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
示例 1:
输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
1
/ \
2 - 3
示例 2:
输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解释: 给定的无向图为:
5 - 1 - 2
| |
4 - 3
注意:
输入的二维数组大小在 3 到 1000。
二维数组中的整数在1到N之间,其中N是输入数组的大小。
解题思路
暴力版:
从最后面向前一个一个删边,如果删掉这个边后,图中无环,则找到了目标边。所以可以将这个问题转换为求无向图中环的问题。
求无向图中的环,用DFS,在DFS栈里保存当前节点的深度,如果新访问到的节点以前访问过,并且节点的深度和原先的节点深度差别不是2的话,那么就出现了环。
这里的2怎么来的呢?因为题目限制了不会有1 -> 3 -> 1
这样的环,所以最短的环必然会是1 -> 3 -> 2 -> 1
,这种时候从1
再次走到1
,走过的路径长度为3
,而从1
走到3
再回头走到1
的路径长度是2,所以可以用这种方法判断无向图中是否存在环。
除了用路径长度,还可以在栈中保存parent
对应的节点,来判断当前的节点是父节点还是环导致的重复出现。
对每次删完边的图,建图需要 o ( N ) o(N) o(N)的复杂度,DFS需要 o ( N ) o(N) o(N)的复杂度,所以总的时间复杂度是 o ( N 2 ) o(N^2) o(N2)
并查集:
每个点是一个集合,从前向后遍历边,把新的边并到并查集里面,第一个还没有并就已经存在的边就是答案。因为这道题说明了答案只能有1个,所以可以这样做,如果有多条边则不能直接返回。
并查集的时间复杂度就比较低了。对n个元素的并查集进行一次操作的复杂度是
o
(
α
(
n
)
)
o(\alpha(n))
o(α(n)),最坏要进行N次操作,所以总的时间复杂度是
o
(
N
α
(
N
)
)
o(N\alpha(N))
o(Nα(N))
代码
暴力版:
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
def exist_loop(graph: dict) -> bool:
visited_nodes = {}
while len(visited_nodes) < len(graph):
remain_nodes = list(set(graph) - set(visited_nodes))
stack = [(remain_nodes[0], 0)]
while stack:
node, depth = stack.pop()
if node in visited_nodes:
if visited_nodes[node] != depth - 2:
return True
continue
visited_nodes[node] = depth
stack += [(neighbor, depth + 1) for neighbor in graph[node]]
return False
# build graph
raw_graph = {}
for from_node, to_node in edges:
if from_node not in raw_graph:
raw_graph[from_node] = []
raw_graph[from_node].append(to_node)
if to_node not in raw_graph:
raw_graph[to_node] = []
raw_graph[to_node].append(from_node)
for index in range(len(edges) - 1, -1, -1):
dropped_graph = raw_graph.copy()
from_node, to_node = edges[index]
dropped_graph[from_node].remove(to_node)
dropped_graph[to_node].remove(from_node)
if not exist_loop(dropped_graph):
return edges[index]
并查集版:
class UnionFindSet:
def __init__(self, N: int) -> None:
self.parent = [i for i in range(N + 1)]
self.height = [0] * (N + 1)
def find(self, x: int) -> int:
if self.parent[x] == x:
return x
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x: int, y: int) -> None:
x, y = self.find(x), self.find(y)
if x == y:
return
if self.height[x] < self.height[y]:
self.parent[x] = self.parent[y]
else:
self.parent[y] = self.parent[x]
if self.height[x] == self.height[y]:
self.height[x] += 1
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
union_find_set = UnionFindSet(len(edges))
for each_edge in edges:
if union_find_set.find(each_edge[0]) == union_find_set.find(each_edge[1]):
return each_edge
union_find_set.union(each_edge[0], each_edge[1])