冗余连接

冗余连接

684. 冗余连接

难度中等

在本问题中, 树指的是一个连通且无环的无向图。

输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

结果图是一个以组成的二维数组。每一个的元素是一对[u, v] ,满足 u < v,表示连接顶点uv无向图的边。

返回一条可以删去的边,使得结果图是一个有着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是输入数组的大小。

更新(2017-09-26): 我们已经重新检查了问题描述及测试用例,明确图是无向 图。对于有向图详见**冗余连接II。**对于造成任何不便,我们深感歉意。

函数签名:

func findRedundantConnection(edges [][]int) []int

分析

并查集

遍历 edges,不断把当前边两端的顶点合并到并查集,合并前先检查是否已经联通,如果已经联通,那么不用再合并,直接返回当前的边。

func findRedundantConnection(edges [][]int) []int {
	uf := make([]int, len(edges)+1)
	for i := range uf {
		uf[i] = i
	}
	for _, e := range edges {
		if !union(uf, e[0], e[1]) {
			return e
		}
	}
	return nil
}

其中的 union 和 find 方法实现如下:

func union(uf []int, x, y int) bool {
	xRoot, yRoot := find(uf, x), find(uf, y)
	if xRoot == yRoot { // 已经联通了
		return false
	}
	uf[xRoot] = uf[yRoot]
	return true
}

func find(uf []int, x int) int {
	for x != uf[x] {
		x, uf[x] = uf[x], uf[uf[x]]
	}
	return x
}

DFS

当然,这个问题也可以用 DFS 或 BFS 的方式解决,如 DFS:

func findRedundantConnection(edges [][]int) []int {
	n := len(edges)
	graph := make([][]int, n+1)
	for _, edge := range edges {
		src, dest := edge[0], edge[1]
		if len(graph[src]) > 0 && len(graph[dest]) > 0 && isConnected(graph, src, dest, make([]bool, n+1)) {
			return edge
		}
		graph[src] = append(graph[src], dest)
		graph[dest] = append(graph[dest], src)
	}
	return nil
}

func isConnected(graph [][]int, src, dest int, seen []bool) bool {
	if seen[src] {
		return false
	}
	if src == dest {
		return true
	}
	seen[src] = true
	for _, nei := range graph[src] {
		if isConnected(graph, nei, dest, seen) {
			return true
		}
	}
	return false
}

685. 冗余连接 II

难度困难

在本问题中,有根树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。每一个节点只有一个父节点,除了根节点没有父节点。

输入一个有向图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

结果图是一个以组成的二维数组。 每一个 的元素是一对 [u, v],用以表示有向图中连接顶点 u 和顶点 v 的边,其中 uv 的一个父节点。

返回一条能删除的边,使得剩下的图是有N个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

示例 1:

输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的有向图如下:
  1
 / \
v   v
2-->3

示例 2:

输入: [[1,2], [2,3], [3,4], [4,1], [1,5]]
输出: [4,1]
解释: 给定的有向图如下:
5 <- 1 -> 2
     ^    |
     |    v
     4 <- 3

注意:

  • 二维数组大小的在3到1000范围内。
  • 二维数组中的每个整数在1到N之间,其中 N 是二维数组的大小。

函数签名:

func findRedundantDirectedConnection(edges [][]int) []int

分析

如果所有节点最多只有一个父节点,那么解法和上面问题(684)完全一样;即只需找到使图出现环的那条边。 否则,先找到有两个父节点的节点 X,结果就是指向 X 的这两条边里的一条,具体是哪一条? 看两个例子:

1 -> 2
|   ^
v /
3

删除 1->2 或 3->2 都可以,根据题意,删除在 edges 数组最后出现的即可

2 -> 1 <- 3
^   /
|  V
 4

如果删除 3->1, 剩余的图就会有一个环; 所以只能删除 2->1

同样可以用并查集或 DFS、BFS的方法,但是用并查集将极大简化代码,同时也简化复杂度~

func findRedundantDirectedConnection(edges [][]int) []int {
	// 如果某个节点有两条入边,这两条入边之一就是答案
	candidates := [2][]int{}
	parent := make([]int, len(edges)+1)
	for _, e := range edges {
		if parent[e[1]] != 0 {
			candidates = [2][]int{ {parent[e[1]], e[1]}, {e[0], e[1]} }
			break
		}
		parent[e[1]] = e[0]
	}
	// 没有有两条入边的节点,只需要找到使图成环的边
	if candidates[0] == nil {
		return detectCycle(edges, nil)
	}
	// 在两条候选边,需要确定返回哪一条
	if detectCycle(edges, candidates[1]) == nil { // 忽略第二条候选边没有在图里发现环
		return candidates[1]
	}
	return candidates[0] // 忽略第二条候选边在图里发现了环
}

func detectCycle(edges [][]int, ignore []int) []int {
	uf := make([]int, len(edges)+1)
	for i := range uf {
		uf[i] = i
	}
	for _, e := range edges {
		if reflect.DeepEqual(e, ignore) {
			continue
		}
		if !union(uf, e[0], e[1]) {
			return e
		}
	}
	return nil
}