1. 问题描述:
在本问题中,有根树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示有向图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
示例 1:
输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]
示例 2:
输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]
提示:
n == edges.length
3 <= n <= 1000
edges[i].length == 2
1 <= ui, vi <= n
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/redundant-connection-ii
2. 思路分析:
这道题目与力扣684题是类似的,这里是有向图,其实多了一个限制条件之后变得就比较复杂了,需要考虑的情况也比较多,主要分为以下三种情况进行讨论:
- 在树中加入了某条边之后形成了环,也即某个节点的边连上了根节点,这个时候去掉环中的任意一个点即可
- 在树中加入了某条边之后没有形成环但是存在了入度为2的边,这个时候我们需要去掉入度为2的其中一条边即可,去掉其中一条边使得某个节点的父节点只有一个
存在上面两种情况,某个节点连上了它的祖先节点,这个时候只能够去掉连向祖先节点的边
我们的任务是需要找出图加入的这条边之后对应是哪一种情况,根据属于哪一种情况返回需要去除哪一条边。具体来说我们需要判断两个问题:
- 判断某条边是否是在环中
- 判断某条边是否是入度为2的边之一
如果上面两种情况都存在那么属于第三种情况,否则属于第一种或者是第二种情况。首先是需要判断某条边是否是入度为2的边之一,这个其实比较好解决,我们只需要枚举整个图,判断当前a==>b的中的点b之前是否被访问过了,访问过了说明当前点的入度为2,所以标记这两边边是度数为2的入边即可(使用st2数组或者列表来记录,st[i]表示第i条边是否是度数为2的入边之一)。
接下来就个比较麻烦的事情,也就是在有向图中找环,并且在数组或者是列表中标记哪一条边是否
3. 代码如下:
from typing import List
import collections
class Solution:
# 使用深搜来找环, 当某次搜索的过程中出现了之前已经标记过的点说明了存在出现了环
def dfs(self, g: collections.defaultdict, u: int):
self.st[u] = 1
self.stk.append(u)
for t in g[u]:
# 当前点没有搜过
if self.st[t] == 0:
if self.dfs(g, t): return True
# 当前点搜过了说明说明出现了环, 我们之前在搜索的时候使用一个栈stk来存储搜索过的元素, 所以这个时候栈中不等于t的元素就是环中的元素, 标记一下
elif self.st[t] == 1:
while self.stk[-1] != t:
# 标记环中的所有点, 这样后面可以根据这个数组找出环中的所有边
self.in_c[self.stk.pop()] = 1
# 加入环中的最后一个节点
self.in_c[t] = 1
# 找到环返回True
return True
# 回溯, 说明没有找到环
self.st[u] = 0
self.stk.pop()
return False
def work1(self, edges: List[List[int]], n: int):
# 深搜需要根据节点的编号来搜索, 而且因为使用的是python语言, 所以需要将点存储到字典中, 键表示节点编号, 值表示节点编号的出边编号
_edges = collections.defaultdict(list)
for e in edges:
a, b = e[0], e[1]
_edges[a].append(b)
for i in range(1, n + 1):
# 判断当前起点出发是否存在环, 找到了就break了
if self.dfs(_edges, i):
break
for i in range(n):
a, b = edges[i][0], edges[i][1]
# 根据深搜的结果找出环中的所有边, 之前dfs搜索的时候标记了环的点这里根据环中的点找到对应的边
if self.in_c[a] and self.in_c[b]:
self.st1[i] = 1
# 求解边是否是入度为2的边之一
def work2(self, edges: List[List[int]], n: int):
p = [-1] * (n + 1)
for i in range(n):
a, b = edges[i][0], edges[i][1]
if p[b] != -1:
self.st2[p[b]] = self.st2[i] = 1
break
p[b] = i
# st1[i]表示当前第i条边是否在环中, st2[i]表示当前的第i条边是否是度数为2的入边之一, st表示标记数组标记在搜索过程中已经访问过的点
# stk用来存储环中元素
st1, st2, stk, in_k, st, in_c = None, None, None, None, None, None
def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
n = len(edges)
self.st1, self.st2 = [0] * (n + 1), [0] * (n + 1)
self.st, self.in_c = [0] * (n + 1), [0] * (n + 1)
self.stk = list()
self.work1(edges, n)
self.work2(edges, n)
# 先判断是否是第三种情况, 也即属于存在环并且环中某个点的入度为2, 因为求解的是最优一条边所以在判断属于哪一种情况都需要逆序遍历
for i in range(n - 1, -1, -1):
if self.st1[i] and self.st2[i]:
return edges[i]
# 不是第三种情况说明属于第一种情况或者是第二种情况, 只要找到满足属于其中一种情况那么直接返回即可
for i in range(n - 1, -1, -1):
if self.st1[i] or self.st2[i]: return edges[i]
return []