1579 - 保证图可完全遍历 - 最小生成树 - 并查集 - 贪心 - 数学分析

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

题目描述

[1579] 保证图可完全遍历

https://leetcode-cn.com/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable/

Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3 种类型的边:

类型 1:只能由 Alice 遍历。
类型 2:只能由 Bob 遍历。
类型 3:Alice 和 Bob 都可以遍历。
给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。

返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。

示例 1:

在这里插入图片描述

输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,3],[1,2,4],[1,1,2],[2,3,4]]
输出:2
解释:如果删除 [1,1,2] 和 [1,1,3] 这两条边,Alice 和 Bob 仍然可以完全遍历这个图。再删除任何其他的边都无法保证图可以完全遍历。所以可以删除的最大边数是 2 。
示例 2:

在这里插入图片描述

输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,4],[2,1,4]]
输出:0
解释:注意,删除任何一条边都会使 Alice 和 Bob 无法完全遍历这个图。
示例 3:

在这里插入图片描述

输入:n = 4, edges = [[3,2,3],[1,1,2],[2,3,4]]
输出:-1
解释:在当前图中,Alice 无法从其他节点到达节点 4 。类似地,Bob 也不能达到节点 1 。因此,图无法完全遍历。

提示:

1 <= n <= 10^5
1 <= edges.length <= min(10^5, 3 * n * (n-1) / 2)
edges[i].length == 3
1 <= edges[i][0] <= 3
1 <= edges[i][1] < edges[i][2] <= n
所有元组 (typei, ui, vi) 互不相同

Related Topics
  • 最小生成树
  • 贪心
  • 数学分析

题目剖析&信息挖掘

此题考查最小生成树特性的应用。

具体实现用到了并查集 并查集学习资料

解题思路

方法一 模拟+贪心+数学

分析

由图论可知一个n结点无向连通图至少要n-1条边

假设最优情况下,类型1选择了 x条,类型2选择了y 条,类型3选择了z条

x+z=n-1 (1)

y+z=n-1 (2)

图上说要删除最多的边

那其实就是要求x+y+z最小。

对1,2式相加并移项

x+y+z=2n-2-z

从上式可以看出 z大越大越好,也就是类型3的边越多越好。

所以,结论就是先把类型3的边用了,再用1,2.

  • 疑问1 有没有可能先用类型3的边然后导致构造不出连通图。

答:先选择3,可以认为在原来1,2的基础上增加了一些边(这种情况只会增加连通的可能性)。根据最小生成树原理,在权值相同的情况下,选择的顺序是不影响连通性的。

  • 疑问2 有没有可能选择了类型3中的某条边,会导致最终结果不是最优的。

答:结论是不会。假设alice, bob现在都已经连通了,也就是他们都选择了 x=n-1, y=n-1, 现在有一条3边过来,

加入到alice, bob以后必然会产生环,所以必然可以拿走一条原来的边。相当于只要类型3选择一条,就可以拿走1,2各一条。从公式也可以反应出来。

思路

func maxNumEdgesToRemove(n int, edges [][]int) int {
	// 排序,把类型为3的排到最前面

	usAlice, usBob := &UnionFindSet{}, &UnionFindSet{} // 初始化alice 和bob的并查集
	usAlice.InitUnionSet(n + 1)                        // 编号从1开始的
	usBob.InitUnionSet(n + 1)

	sum := 0 // 去除的边
	for _, edge := range edges {
		switch edge[0] {
		case 3:
			if !usAlice.UnionV2(edge[1], edge[2]) { // 加不上, 删除一条边
				sum++
			}
			usBob.UnionV2(edge[1], edge[2]) // bob同步加上

		case 2:
			// 往bob上加
			if !usBob.UnionV2(edge[1], edge[2]) { // 加不上删除一条
				sum++
			}
		case 1:
			// 往alice上加
			if !usAlice.UnionV2(edge[1], edge[2]) { // 加不上删除一条
				sum++
			}
		}
	}

	// 判断是不是都连通了
	if !isLinked(usAlice, n) || !isLinked(usBob, n) {
		// 至少有一个未连通
		return -1
	}

	return sum
}
注意
  • 不连通情况下优先返回-1。
  • 考虑n=1的情况。
  • 类型3的边只算一条边。
知识点
  • 最小生成树
  • 贪心
  • 数学分析
复杂度
  • 时间复杂度:O(edges)
  • 空间复杂度:O(n+edges)
参考

并查集学习资料

代码实现

/*
并查集,判连通用
*/
type UnionFindSet struct {
	father  []int // 存储结点的父亲
	height  []int // 存储结点的父亲
	nodeNum int   // 总结点个数
}

func (us *UnionFindSet) InitUnionSet(n int) {
	us.nodeNum = n + 1 // 不加也可以,有人喜欢以0开头
	us.father = make([]int, us.nodeNum)
	us.height = make([]int, us.nodeNum)
	for i, _ := range us.father {
		us.father[i] = i
		us.height[i] = 1
	}
}

func (us *UnionFindSet) FindV2(x int) int {
	root := x // 保存好路径上的头结点
	for us.father[root] != root {
		root = us.father[root]
	}
	/*
		从头结点开始一直往根上遍历
		把所有结点的father直接指向root。
	*/
	for us.father[x] != x {
		// 一定要先保存好下一个结点,下一步是要对us.father[x]进行赋值
		temp := us.father[x]
		us.father[x] = root
		x = temp
	}

	return root
}

/*
需要加入height[]属性,初始化为1.
*/
//合并结点
func (us *UnionFindSet) UnionV2(x, y int) bool {
	x = us.FindV2(x)
	y = us.FindV2(y)
	if x == y {
		return false
	}
	if us.height[x] < us.height[y] {
		us.father[x] = y
	} else if us.height[x] > us.height[y] {
		us.father[y] = x
	} else {
		us.father[x] = y
		us.height[y]++
	}
	return true
}

func isLinked(us *UnionFindSet, n int) bool {
	rootNum :=0
	for i:=1;i<=n;i++ {
		if us.FindV2(i)==i{
			rootNum++
		}
	}

	return rootNum==1
}

func maxNumEdgesToRemove(n int, edges [][]int) int {
	// 排序,把类型为3的排到最前面
	sort.Slice(edges, func(i, j int) bool {
		return edges[i][0] > edges[j][0]
	})

	usAlice, usBob := &UnionFindSet{}, &UnionFindSet{} // 初始化alice 和bob的并查集
	usAlice.InitUnionSet(n + 1)                        // 编号从1开始的
	usBob.InitUnionSet(n + 1)

	sum := 0 // 去除的边
	for _, edge := range edges {
		switch edge[0] {
		case 3:
			if !usAlice.UnionV2(edge[1], edge[2]) { // 加不上, 删除一条边
				sum++
			}
			usBob.UnionV2(edge[1], edge[2]) // bob同步加上

		case 2:
			// 往bob上加
			if !usBob.UnionV2(edge[1], edge[2]) { // 加不上删除一条
				sum++
			}
		case 1:
			// 往alice上加
			if !usAlice.UnionV2(edge[1], edge[2]) { // 加不上删除一条
				sum++
			}
		}
	}

	// 判断是不是都连通了
	if !isLinked(usAlice, n) || !isLinked(usBob, n) {
		// 至少有一个未连通
		return -1
	}

	return sum
}

相关题目

并查集学习资料 这里整理了资料


本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值