并查集解题记录128最长连续序列200岛屿数量547省份数量684冗余连接
首先默写一遍并查集模板(杭电ACM队刘老师那个B站视频讲的很适合入门,理解并查集在干什么)
type UnionFind struct {
parent []int // 同一个集合里的头,功能就是标注在哪个集合里
count int
}
func (uf *UnionFind) Init(n int) {
// 根据数目初始化每个点
uf.count = n
uf.parent = make([]int, n)
for i:= range uf.parent {
uf.parent[i] = i // 每个元素父节点都是他自己
}
}
// 查找属于哪一个集合中
func (uf *UnionFind) Find(p int) int {
if uf.parent[p] != p {
uf.parent[p] = uf.Find(uf.parent[p]) // 路径压缩
}
return uf.parent[p]
}
func (uf *UnionFind) Union(p, q int) {
rootP := uf.Find(p)
rootQ := uf.Find(q)
if rootP != rootQ {
uf.parent[rootP] = rootQ // 将一个集合的根节点指向另一个集合的根节点
uf.count-- // 合并后集合数量减少
}
}
// TotalCount 获取当前集合的数量
func (uf *UnionFind) TotalCount() int {
return uf.count
}
这个模板没有实现秩优化。可以将深度小的树合并到深度大的树上,或者将节点数少的树合并到节点数多的树上,这样做的目的是让树保持尽可能平衡,避免形成深度很大的树,确保操作的效率。
然后进一步展示一下路径优化在干啥,便于默写出代码:
假设:parent = [0, 0, 1, 2, 3]
表示: 0 ← 1 ← 2 ← 3 ← 4
如果直接 Find(4),你会递归查询:4 → 3 → 2 → 1 → 0(需要 4 步)
如果下次再 Find(4),仍然要 4 步,效率很低!
先做比较简单的母题那种的547省份数量:
// 默写并查集模板
type UnionFind struct {
parent []int
count int
}
func (uf *UnionFind) Init (total int) {
uf.parent = make([]int, total)
uf.count = total
for i,_ := range uf.parent {
uf.parent[i] = i
}
}
func (uf *UnionFind) Find (q int) int {
if q != uf.parent[q] {
uf.parent[q] = uf.Find(uf.parent[q])
}
return uf.parent[q]
}
func (uf *UnionFind) Union (p, q int) {
rootp := uf.Find(p)
rootq := uf.Find(q)
if rootp != rootq {
uf.parent[rootp] = rootq
uf.count--
}
}
func (uf *UnionFind) SumCount () int {
return uf.count
}
func findCircleNum(isConnected [][]int) int {
uf := &UnionFind{}
uf.Init(len(isConnected))
for i:=0 ; i<len(isConnected); i++ {
for j:=0; j<len(isConnected[0]); j++ {
if isConnected[i][j]==1 {
uf.Union(i,j)
}
}
}
res := uf.SumCount()
return res
}
现在来思考128最长连续序列。对这道题的思路是先将数据都放入map,然后遍历数组查找每个元素是否有v+1和v-1的元素存在,如果存在就合并这个集合。但是好像会使得集合中存在重复的元素?那就去遍历map而不是数组。还要考虑一个问题,得到并查集后如何统计集合大小?维护一个size数组,记录每个集合的大小,在union时更新。
// 默写一个并查集模板
type UnionFind struct {
parent []int
size []int
count int
}
func (uf *UnionFind) Init (total int) {
uf.parent = make([]int, total)
uf.size = make([]int, total) // 初始每个并查集大小为1
for i:=0; i<total; i++ {
uf.parent[i] = i
uf.size[i] = 1
}
uf.count = total
}
func (uf *UnionFind) Find (q int) int {
if q != uf.parent[q] {
uf.parent[q] = uf.Find(uf.parent[q])
}
return uf.parent[q]
}
func (uf *UnionFind) Union (p,q int) {
rootQ := uf.Find(q)
rootP := uf.Find(p)
if rootP != rootQ {
uf.count--
uf.parent[rootP] = rootQ // 将一个集合的根节点指向另一个集合的根节点
uf.size[rootQ] += uf.size[rootP] // 累加整个集合大小
}
}
func (uf *UnionFind) maxSize () int {
max := 1
for i:=0; i<len(uf.size); i++ {
if uf.size[i] > max {
max = uf.size[i]
}
}
return max
}
func longestConsecutive(nums []int) int {
// 检查为空的情况
if len(nums) == 0{
return 0
}
// 去重且放入map用于查找
// key是用于查找的数值,v是在并查集里面的index,初始化为递增
m := make(map[int]int)
p := 0
for _, value := range nums {
if _, ok := m[value]; !ok {
m[value] = p
p += 1
}
}
// 组织并查集
uf := &UnionFind{}
uf.Init(len(m))
for k,index := range m {
if v,ok := m[k+1]; ok {
uf.Union(index, v)
}
if v,ok := m[k-1]; ok {
uf.Union(index, v)
}
}
max := uf.maxSize()
return max
}
684.冗余连接 这道题读起来感觉像去掉一条边,使得并查集依然为1个。倒着来选择一条边不被遍历,有点暴力。或者有没有办法做并查集的拆分?应该不可能这么幺蛾子。看了佬的思路,说的是:依次扫描所有的边,把边的两端点都合并 union() 到一起。如果遇到一条边的两端点已经在一个集合里面了,就说明是多余边,删除。最后输出这些边即可。 对啊,可以把不关键的边又收集起来,然后输出最后一个即可。
// 先默写并查集模板
type UnionFind struct {
parent []int
// count int
}
func (uf *UnionFind) Init (total int) {
// uf.count = total
uf.parent = make([]int, total+1) // 坑!对于n条边的图,节点编号是1到n
for i,_ := range uf.parent {
uf.parent[i] = i
}
}
func (uf *UnionFind) Find (q int) int {
if q != uf.parent[q] {
uf.parent[q] = uf.Find(uf.parent[q])
}
return uf.parent[q]
}
func (uf *UnionFind) Union (p,q int) bool {
rootp := uf.Find(p)
rootq := uf.Find(q)
if rootp != rootq {
uf.parent[rootp] = rootq
// uf.count--
return false
} else {
// 这条边冗余的
return true
}
}
func findRedundantConnection(edges [][]int) []int {
uf := &UnionFind{}
uf.Init(len(edges))
var arr []int // 记录冗余边的index
for i, v := range edges {
if uf.Union(v[0], v[1]) {
arr = append(arr, i)
}
}
res := edges[arr[len(arr)-1]]
return res
}
200岛屿数量应该是指把水平和竖直方向有关联的加入一个集合,最后返回集合数量。需要将二维问题映射到一维并查集。(但是我感觉dfs或者bfs更好通过吧,到时候来补上)(过段时间再写这个解法这样就能巩固一下并查集模板)