十、leetcode刷题之【并查集 Union-Find】

[TOC]

基础知识

https://labuladong.gitee.io/algo/2/22/53/

https://leetcode.cn/circle/discuss/qmjuMW/

B站上古城算法讲的也很不错;

并查集是一种树形的数据结构,用于处理不交集的合并(union)和查询(find)问题

Find:确认元素属于哪一个子集,确认两个元素是不是同一个子集

Union:将两个子集合并为同一个集合

找到root parent,也就是根父母

比如:

TreeNode1 := TreeNode{Val:3,Left:  &TreeNode{1, nil, nil},Right: &TreeNode{2,nil, nil }}
3是root,3的parent=3,1和2的parent=3

TreeNode2 := TreeNode{Val:4,Left:  &TreeNode{5, nil, nil},Right: nil}
4是root,4的parent=4,5的parent=4

union(1,5)=> 找到1的root parent=3,找到5的root parent=4, 也就是3做4的孩子,或者3做4的父母,都可以,合并两个子集。

基础模板伪代码

初始化

func makeSet(x)
  x.parent=x

查询

func Find(x)
 if x.parent==x{return x}
 else return Find(x.parent)

合并(找到root parent,然后其中一个root parent做另外一个的kid)

func Union(x,y)
  xRoot:=Find(x)
  yRoot:=Find(y)
  x.Root.parent=yRoot

基础模板

type UF struct {
    count  int
    parent []int
}

// 并查集模板
func NewUF(x int) *UF {
    u := UF{
        count:  x,  
        parent: make([]int, x),
    }
    for i := 0; i < x; i++ {
        u.parent[i] = i
    }
    return &u
}

// 查询,找root parent
func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

// 合并普通版
func (u *UF) Union_simple(a, b int) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

// (可选)判断两个节点有没有连接
func (u *UF) Connected(a, b int) bool {
    return u.Find(a) == u.Find(b)
}

//(可选),其实一般都直接是u.count
func (u *UF) Count() int {
    return u.count
}

//(可选)
func (u *UF) Add() {
    u.count++
}

weight优化模板

type UF_weight struct {
    count  int
    parent []int
    weight []int
}

// 并查集模板
func NewUF_weight(x int) *UF_weight {
    u := UF_weight{
        count:  x, 
        parent: make([]int, x),
        weight: make([]int, x),
    }
    for i := 0; i < x; i++ {
        u.parent[i] = i
        u.weight[i] = 1
    }
    return &u
}

// 查询,找root parent
func (u *UF_weight) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

// 合并优化版,利用weight
func (u *UF_weight) Union_weight(a, b int) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    if u.weight[rootA] < u.weight[rootB] {
        u.parent[rootA] = rootB
        u.weight[rootB] += u.weight[rootA]
    } else {
        u.parent[rootB] = rootA
        u.weight[rootA] += u.weight[rootB]
    }
    u.count--
}

//(可选)判断两个节点有没有连接
func (u *UF_weight) Connected(a, b int) bool {
    return u.Find(a) == u.Find(b)
}

//(可选)
func (u *UF_weight) Count() int {
    return u.count
}

//(可选)
func (u *UF_weight) Add() {
    u.count++
}

rank优化模板

type UF_rank struct {
    count  int
    parent []int
    rank []int
}

// 并查集模板
func NewUF_rank(x int) *UF_rank {
    u := UF_rank{
        count:  x,  
        parent: make([]int, x),
        rank: make([]int, x),
    }
    for i := 0; i < x; i++ {
        u.parent[i] = i
        u.rank[i] = 1
    }
    return &u
}

// 查询,找root parent
func (u *UF_rank) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

 
// 合并优化版,利用rank
func (u *UF_rank) Union_rank(a, b int) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    if u.rank[rootA] < u.rank[rootB] {
        u.parent[rootA] = rootB
    } else if u.rank[rootA] > u.rank[rootB] {
        u.parent[rootB] = rootA
    } else { // 只有rank相等的情况下合并后才需要维护rank,rank需要+1
        u.parent[rootA] = rootB
        u.rank[rootB]++
    }
    u.count--
}


// (可选)判断两个节点有没有连接
func (u *UF_rank) Connected(a, b int) bool {
    return u.Find(a) == u.Find(b)
}

//(可选)
func (u *UF_rank) Count() int {
    return u.count
}

//(可选)
func (u *UF_rank) Add() {
    u.count++
}

字符串模板(可参考737题)


// ======================= 以下这个模板也比较经典,是和string有关的(注意要记住)  =========================
type UF struct {
    count  int
    parent map[string]string
}

func NewUF(x int, sentence1 []string, sentence2 []string) *UF {
    u := UF{
        count:  x,
        parent: make(map[string]string, x),  //这个是核心
    }
    for i := 0; i < x; i++ {
        u.parent[sentence1[i]] = sentence1[i]
        u.parent[sentence2[i]] = sentence2[i]
    }
    return &u
}

func (u *UF) Find(x string) string {
    if strings.Compare(u.parent[x], x) != 0 { // u.parent[x]==x,strings.Compare结果=0
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

func (u *UF) Union(a, b string) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

func (u *UF) Connected(a, b string) bool {
    return u.Find(a) == u.Find(b)
}

Leetcode刷题

323. 无向图中连通分量的数目(会员/中等)

你有一个包含 n 个节点的图。给定一个整数 n 和一个数组 edges ,其中 edges[i] = [ai, bi] 表示图中 ai 和 bi 之间有一条边。
返回 图中已连接分量的数目 。
输入: n = 5, edges = [[0, 1], [1, 2], [3, 4]]
输出: 2

输入: n = 5, edges = [[0,1], [1,2], [2,3], [3,4]]
输出:  1
func main() {
    n := 5
    edges := [][]int{{0, 1}, {1, 2}, {3, 4}}
    fmt.Println(countComponents(n, edges))
}

func countComponents(n int, edges [][]int) int {
    obj := NewUF(n)
    for _, v := range edges {
        obj.Union(v[0], v[1])
    }
    return obj.count
}

type UF struct {
    parent []int
    count  int
}

func NewUF(x int) *UF {
    parent := make([]int, x)
    for i := 0; i < x; i++ {
        parent[i] = i
    }
    count := x
    return &UF{parent: parent, count: count}
}

func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

func (u *UF) Union(a, b int) {
    rootA := u.Find(a)
    rootB := u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

547. 省份数量(中等)

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。返回矩阵中 省份 的数量。

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
// 这一题很明显需要用并查集来做,而不是利用200题岛屿数量的DFS来套用,虽然可以用DFS来解题,但是需要变化。
func findCircleNum(isConnected [][]int) (ans int) {
    n := len(isConnected)
    obj := NewUF(n)
    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            if isConnected[i][j] == 1 {
                obj.Union_simple(i, j)
            }
        }
    }
    return obj.count
}

type UF struct {
    count  int
    parent []int
}

// 并查集模板
func NewUF(x int) *UF {
    u := UF{
        count:  x, // 与模板不一样的地方:初始没有岛屿(题目要求初始都是海)
        parent: make([]int, x),
    }
    for i := 0; i < x; i++ {
        u.parent[i] = i
    }
    return &u
}

// 查询,找root parent
func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

// 合并普通版
func (u *UF) Union_simple(a, b int) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

305. 岛屿数量 II(会员/困难)

给你一个大小为 m x n 的二进制网格 grid 。网格表示一个地图,其中,0 表示水,1 表示陆地。最初,grid 中的所有单元格都是水单元格(即,所有单元格都是 0)。可以通过执行 addLand 操作,将某个位置的水转换成陆地。给你一个数组 positions ,其中 positions[i] = [ri, ci] 是要执行第 i 次操作的位置 (ri, ci) 。返回一个整数数组 answer ,其中 answer[i] 是将单元格 (ri, ci) 转换为陆地后,地图中岛屿的数量。岛屿 的定义是被「水」包围的「陆地」,通过水平方向或者垂直方向上相邻的陆地连接而成。你可以假设地图网格的四边均被无边无际的「水」所包围。

输入:m = 3, n = 3, positions = [[0,0],[0,1],[1,2],[2,1]]
输出:[1,1,2,3]
解释:
起初,二维网格 grid 被全部注入「水」。(0 代表「水」,1 代表「陆地」)
- 操作 #1:addLand(0, 0) 将 grid[0][0] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #2:addLand(0, 1) 将 grid[0][1] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #3:addLand(1, 2) 将 grid[1][2] 的水变为陆地。此时存在 2 个岛屿。
- 操作 #4:addLand(2, 1) 将 grid[2][1] 的水变为陆地。此时存在 3 个岛屿。

[图片上传失败...(image-943ace-1671479296836)]

func main() {
    m := 3
    n := 3
    positions1 := [][]int{{0, 0}, {0, 1}, {1, 2}, {2, 1}}
    fmt.Println(numIslands2_personal(m, n, positions1))

    positions2 := [][]int{{0, 0}, {0, 1}, {1, 2}, {1, 2}}
    fmt.Println(numIslands2_personal(m, n, positions2))

}

func numIslands2_personal(m int, n int, positions [][]int) []int {
    u := NewUF(m * n) // 构造函数
    grid := make([][]int, m)
    for i := 0; i < m; i++ {
        grid[i] = make([]int, n)
    }

    var res []int
    var inGrid = func(x, y int) bool {
        return x >= 0 && y >= 0 && x < m && y < n
    }

    dirs := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}
    for _, p := range positions {
        x, y := p[0], p[1]
        index := x*n + y // 用 index=x∗n+y 表示 (x,y) 这个点。

        // 这一段用来干嘛的,主要是判断输入有没有重复的,重复的可以直接返回结果,比如 positions2 := [][]int{{0, 0}, {0, 1}, {1, 2}, {1, 2}}
        if grid[x][y] == 1 {
            res = append(res, u.Count())
            continue
        }

        grid[x][y] = 1 // 填岛屿了
        u.Add()

        for _, v := range dirs {
            newRow := x + v[0]
            newCol := y + v[1]
            Index := newRow*n + newCol
            if inGrid(newRow, newCol) && grid[newRow][newCol] == 1 && !u.Connected(index, Index) { // 如果其中一个方向有已经填过的岛屿,且没有连接在一起,那么就可以将填海位置与已经填过的岛屿连接在一起,同时孤岛数量-1(在Union里面自动做掉)
                u.Union_weight(index, Index)
            }
        }
        res = append(res, u.Count())
    }
    return res
}

func numIslands2(m int, n int, positions [][]int) []int {
    u := NewUF(m * n)            // 构造函数
    visited := make([]bool, m*n) // 二维变一维,这里最精髓的地方就是二维问题转化为一维问题
    var res []int

    var inGrid = func(x, y int) bool {
        return x >= 0 && y >= 0 && x < m && y < n
    }

    dirs := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}

    for _, p := range positions {
        x, y := p[0], p[1]
        index := x*n + y // 用 index=x∗n+y 表示 (x,y) 这个点。

        // 这一段用来干嘛的,主要是判断输入有没有重复的,重复的可以直接返回结果,比如 positions2 := [][]int{{0, 0}, {0, 1}, {1, 2}, {1, 2}}
        if visited[index] {
            res = append(res, u.Count())
            continue
        }

        visited[index] = true // 填岛屿
        u.Add()

        for _, v := range dirs {
            newRow := x + v[0]
            newCol := y + v[1]
            Index := newRow*n + newCol
            if inGrid(newRow, newCol) && visited[Index] && !u.Connected(index, Index) { // 如果其中一个方向有已经填过的岛屿,且没有连接在一起,那么就可以将填海位置与已经填过的岛屿连接在一起,同时孤岛数量-1(在Union里面自动做掉)
                u.Union_weight(index, Index)
            }
        }
        res = append(res, u.Count())
    }
    return res
}

// ============  以下是并查集模板  =========================
type UF struct {
    count  int
    parent []int
    weight []int
}

// 并查集模板
func NewUF(x int) *UF {
    u := UF{
        count:  0, // 与模板不一样的地方:初始没有岛屿(题目要求初始都是海),这里初始化需要注意下 
        parent: make([]int, x),
        weight: make([]int, x),
    }
    for i := 0; i < x; i++ {
        u.parent[i] = i
        u.weight[i] = 1  // 这里初始化的weight也要注意,是1,不是i
    }
    return &u
}

// 查询,找root parent
func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

// 合并优化版,利用weight
func (u *UF) Union_weight(a, b int) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    if u.weight[rootA] < u.weight[rootB] {
        u.parent[rootA] = rootB
        u.weight[rootB] += u.weight[rootA]
    } else {
        u.parent[rootB] = rootA
        u.weight[rootA] += u.weight[rootB]
    }
    u.count--
}

// 判断两个节点有没有连接
func (u *UF) Connected(a, b int) bool {
    return u.Find(a) == u.Find(b)
}

func (u *UF) Count() int {
    return u.count
}

func (u *UF) Add() {
    u.count++
}

1101. 彼此熟识的最早时间(会员/中等)

在一个社交圈子当中,有 n 个人。每个人都有一个从 0 到 n - 1 的唯一编号。我们有一份日志列表 logs,其中 logs[i] = [timestampi, xi, yi] 表示 xi 和 yi 将在同一时间 timestampi 成为朋友。友谊是相互的。也就是说,如果 a 和 b 是朋友,那么 b 和 a 也是朋友。同样,如果 a 和 b 是朋友,或者 a 是 b 朋友的朋友 ,那么 a 和 b 是熟识友。返回圈子里所有人之间都熟识的最早时间。如果找不到最早时间,就返回 -1 。

输入:logs = [[20190101,0,1],[20190104,3,4],[20190107,2,3],[20190211,1,5],[20190224,2,4],[20190301,0,3],[20190312,1,2],[20190322,4,5]], N = 6
输出:20190301
解释:
第一次结交发生在 timestamp = 20190101,0 和 1 成为好友,社交朋友圈如下 [0,1], [2], [3], [4], [5]。
第二次结交发生在 timestamp = 20190104,3 和 4 成为好友,社交朋友圈如下 [0,1], [2], [3,4], [5].
第三次结交发生在 timestamp = 20190107,2 和 3 成为好友,社交朋友圈如下 [0,1], [2,3,4], [5].
第四次结交发生在 timestamp = 20190211,1 和 5 成为好友,社交朋友圈如下 [0,1,5], [2,3,4].
第五次结交发生在 timestamp = 20190224,2 和 4 已经是好友了。
第六次结交发生在 timestamp = 20190301,0 和 3 成为好友,大家都互相熟识了。

输入: logs = [[0,2,0],[1,0,1],[3,0,3],[4,1,2],[7,3,1]], n = 4
输出: 3

func main() {
    logs := [][]int{
        {20190101, 0, 1},
        {20190104, 3, 4},
        {20190107, 2, 3},
        {20190211, 1, 5},
        {20190224, 2, 4},
        {20190301, 0, 3},
        {20190312, 1, 2},
        {20190322, 4, 5}}
    N := 6
    fmt.Println(earliestAcq(logs, N))

    logs1 := [][]int{{9, 3, 0}, {0, 2, 1}, {8, 0, 1}, {1, 3, 2}, {2, 2, 0}, {3, 3, 1}}
    N1 := 4
    fmt.Println(earliestAcq(logs1, N1))
}

func earliestAcq(logs [][]int, n int) int {
    obj := NewUF(n)
    sort.Slice(logs, func(i, j int) bool { // 这里按照第一列进行排序非常重要,因为不一定时间先的放在数组前面了
        return logs[i][0] < logs[j][0]
    })

    fmt.Println(logs)

    for _, v := range logs {
        obj.Union(v[1], v[2])
        if obj.count == 1 {
            return v[0]
        }
    }
    return -1
}

type UF struct {
    parent []int
    count  int
}

func NewUF(x int) *UF {
    parent := make([]int, x)
    for i := 0; i < x; i++ {
        parent[i] = i
    }
    count := x
    return &UF{parent: parent, count: count}
}

func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

func (u *UF) Union(a, b int) {
    rootA := u.Find(a)
    rootB := u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

1135. 最低成本联通所有城市(会员/中等)

想象一下你是个城市基建规划者,地图上有 n 座城市,它们按以 1 到 n 的次序编号。给你整数 n 和一个数组 conections,其中 connections[i] = [xi, yi, costi] 表示将城市 xi 和城市 yi 连接所要的costi(连接是双向的)。返回连接所有城市的最低成本,每对城市之间至少有一条路径。如果无法连接所有 n 个城市,返回 -1. 该 最小成本 应该是所用全部连接成本的总和。

输入:n = 3, conections = [[1,2,5],[1,3,6],[2,3,1]]
输出:6
解释:选出任意 2 条边都可以连接所有城市,我们从中选取成本最小的 2 条。

输入:n = 4, conections = [[1,2,3],[3,4,4]]
输出:-1
解释:即使连通所有的边,也无法连接所有城市。
func main() {
    n := 3
    conections := [][]int{{1, 2, 5}, {1, 3, 6}, {2, 3, 1}}
    fmt.Println(minimumCost(n, conections))
}

func minimumCost(n int, connections [][]int) int {
    obj := NewUF(n + 1) // 这里需要注意的是城市是从1开始的,也就是三个城市,1,2,3,必须要+1,避免panic.
    sort.Slice(connections, func(i, j int) bool { // 这里按照第一列进行排序非常重要,因为不一定时间先的放在数组前面了
        return connections[i][2] < connections[j][2]
    })

    fmt.Println(connections)
    res := 0
    for _, v := range connections {
        if !obj.Connected(v[0], v[1]) {  // 其实这里还有一点贪心算法的意思,如果不连,就连通。不能无脑连通,有可能之前已经是连通状态了
            obj.Union(v[0], v[1])
            res += v[2]
        }

        if obj.count == 2 { // 这里需要注意的是城市是从1开始的,也就是三个城市,1,2,3  
            return res
        }
    }
    return -1
}

type UF struct {
    parent []int
    count  int
}

func NewUF(x int) *UF {
    parent := make([]int, x)
    for i := 0; i < x; i++ {
        parent[i] = i
    }
    count := x
    return &UF{parent: parent, count: count}
}

func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

func (u *UF) Union(a, b int) {
    rootA := u.Find(a)
    rootB := u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

func (uf *UF) Connected(a, b int) bool {
    return uf.Find(a) == uf.Find(b)
}

1061. 按字典序排列最小的等效字符串(会员/中等)

给出长度相同的两个字符串s1 和 s2 ,还有一个字符串 baseStr 。其中  s1[i] 和 s2[i]  是一组等价字符。举个例子,如果 s1 = "abc" 且 s2 = "cde",那么就有 'a' == 'c', 'b' == 'd', 'c' == 'e'。
等价字符遵循任何等价关系的一般规则:
自反性 :'a' == 'a'
对称性 :'a' == 'b' 则必定有 'b' == 'a'
传递性 :'a' == 'b' 且 'b' == 'c' 就表明 'a' == 'c'
例如, s1 = "abc" 和 s2 = "cde" 的等价信息和之前的例子一样,那么 baseStr = "eed" , "acd" 或 "aab",这三个字符串都是等价的,而 "aab" 是 baseStr 的按字典序最小的等价字符串. 利用 s1 和 s2 的等价信息,找出并返回 baseStr 的按字典序排列最小的等价字符串。

输入:s1 = "parker", s2 = "morris", baseStr = "parser"
输出:"makkek"
解释:根据 A 和 B 中的等价信息,我们可以将这些字符分为 [m,p], [a,o], [k,r,s], [e,i] 共 4 组。每组中的字符都是等价的,并按字典序排列。所以答案是 "makkek"。

输入:s1 = "hello", s2 = "world", baseStr = "hold"
输出:"hdld"
解释:根据 A 和 B 中的等价信息,我们可以将这些字符分为 [h,w], [d,e,o], [l,r] 共 3 组。所以只有 S 中的第二个字符 'o' 变成 'd',最后答案为 "hdld"。

输入:s1 = "leetcode", s2 = "programs", baseStr = "sourcecode"
输出:"aauaaaaada"
解释:我们可以把 A 和 B 中的等价字符分为 [a,o,e,r,s,c], [l,p], [g,t] 和 [d,m] 共 4 组,因此 S 中除了 'u' 和 'd' 之外的所有字母都转化成了 'a',最后答案为 "aauaaaaada"。
func main() {
    s1 := "parker"
    s2 := "morris"
    baseStr := "parser"
    fmt.Println(smallestEquivalentString(s1, s2, baseStr))
}
// 这个思路有点绝啊,将字符转化为数字
func smallestEquivalentString(s1 string, s2 string, baseStr string) string {
    s1Len := len(s1)
    obj := NewUF(26) // 这里非常关键,为什么是26呢。因为是字母,最多26个需要找父节点
    for i := 0; i < s1Len; i++ {
        obj.Union(int(s1[i]-'a'), int(s2[i]-'a'))
    }

    res := []byte{}
    for i := range baseStr {
        res = append(res, byte('a'+obj.Find(int(baseStr[i]-'a'))))
    }
    return string(res)
}

// 模板
type UF struct {
    count  int
    parent []int
}

func NewUF(x int) *UF {
    u := UF{
        count:  x,
        parent: make([]int, x),
    }
    for i := 0; i < x; i++ {
        u.parent[i] = i
    }
    return &u
}

func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

// 此题在这里最基础的模板针对题目进行了优化,也就是要找最小的parent
func (u *UF) Union(a, b int) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    if rootA < rootB { // 为了找最小的parent
        u.parent[rootB] = rootA
    } else {
        u.parent[rootA] = rootB
    }
    u.count--
}

func (u *UF) Connected(a, b int) bool {
    return u.Find(a) == u.Find(b)
}

1102. 得分最高的路径(会员/中等)

给定一个 m x n 的整数矩阵 grid,返回从 (0,0) 开始到 (m - 1, n - 1) 在四个基本方向上移动的路径的最大 分数 。一条路径的 分数 是该路径上的最小值。例如,路径 8 → 4 → 5 → 9 的得分为 4 。

输入:grid = [[5,4,5],[1,2,6],[7,4,6]]
输出:4
解释:得分最高的路径用黄色突出显示。 

输入:grid = [[2,2,1,2,2,2],[1,2,2,2,1,2]]
输出:2

输入:grid = [[3,4,6,3,4],[0,2,1,1,7],[8,8,3,2,7],[3,2,4,9,8],[4,1,2,0,0],[4,6,5,4,3]]
输出:3

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/path-with-maximum-minimum-value
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

[图片上传失败...(image-388dda-1671479296836)]


// ================== 解法一: 并查集  =====================
// 这里其实很关键,就相当于从大到小挨个中,后来看是不是能连通
// 1. 把节点值 从大到小 逐个尝试,直到最后拼出来grid[0,0]到grid[row-1, col-1](从左上角到右下角)的通路。
// 2. 进行UF,期间记录最小值
// 2. 如果最后通了,那么返回res
func maximumMinimumPath(grid [][]int) int {
    row, col := len(grid), len(grid[0])
    array := make([][]int, 0)
    for i := 0; i < row; i++ {
        for j := 0; j < col; j++ {
            temp := []int{i, j, grid[i][j]}
            array = append(array, temp)
        }
    }

    fmt.Println(array)

    sort.Slice(array, func(i, j int) bool { // 按照二维数组的值从大到小排列
        return array[i][2] > array[j][2]
    })
    

    obj := NewUF(row * col)
    
    visited := make([]bool, row*col)
    visited[0] = true
    visited[row*col-1] = true
    
    res := min(grid[0][0], grid[row-1][col-1])
    directions := [][]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}
    
    for _, node := range array {
        x, y := node[0], node[1]
        index := x*col + y
        res = min(res, node[2])
        visited[index] = true
        for _, dir := range directions {
            newX := x + dir[0]
            newY := y + dir[1]
            newIndex := newX*col + newY
            if newX >= 0 && newX < row && newY >= 0 && newY < col && visited[newIndex] {
                obj.Union(index, newIndex)
            }
    
            if obj.Connected(0, row*col-1) { // 左上和右下连通了,退出
                return res
            }
        }
    }
    return res

}

// 模板
type UF struct {
    count  int
    parent []int
}

func NewUF(x int) *UF {
    u := UF{
        count:  x,
        parent: make([]int, x),
    }
    for i := 0; i < x; i++ {
        u.parent[i] = i
    }
    return &u
}

func (u *UF) Find(x int) int {
    if u.parent[x] != x {
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

func (u *UF) Union(a, b int) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

func (u *UF) Connected(a, b int) bool {
    return u.Find(a) == u.Find(b)
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

====================  上述是利用数组的,也可以转化为node结构体来   ====================
type node struct {
    value int
    row   int
    col   int
}

func maximumMinimumPath(grid [][]int) int {
    row, col := len(grid), len(grid[0])
    obj := NewUF(row * col)

    var nodes []node
    for i := 0; i < row; i++ {
        for j := 0; j < col; j++ {
            nodes = append(nodes, node{value: grid[i][j], row: i, col: j})
        }
    }
    sort.Slice(nodes, func(i, j int) bool { // 按照二维数组的值从大到小排列
        return nodes[i].value > nodes[j].value
    })

    fmt.Println(nodes)

    visited := make([]bool, row*col)
    visited[0] = true
    visited[row*col-1] = true

    res := min(grid[0][0], grid[row-1][col-1])
    directions := [][]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}

    for _, node := range nodes {
        x, y := node.row, node.col
        index := x*col + y
        res = min(res, node.value)
        visited[index] = true
        for _, dir := range directions {
            newX := x + dir[0]
            newY := y + dir[1]
            newIndex := newX*col + newY
            if newX >= 0 && newX < row && newY >= 0 && newY < col && visited[newIndex] {
                obj.Union(index, newIndex)
            }
            
            if obj.Connected(0, row*col-1) { // 左上和右下连通了,退出
                return res
            }
        }
    }
    return res

}


261. 以图判树(会员/中等)

给定编号从 0 到 n - 1 的 n 个结点。给定一个整数 n 和一个 edges 列表,其中 edges[i] = [ai, bi] 表示图中节点 ai 和 bi 之间存在一条无向边。如果这些边能够形成一个合法有效的树结构,则返回 true ,否则返回 false 。

输入: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
输出: true

输入: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]
输出: false
func main() {
    n := 5
    edges := [][]int{{0, 1}, {0, 2}, {0, 3}, {1, 4}}
    fmt.Println(validTree(n, edges))

    edge2 := [][]int{{0, 1}, {1, 2}, {2, 3}, {1, 3}, {1, 4}} // 1,2,3形成了一个环,所以会挂。
    fmt.Println(validTree(n, edge2))
}

func validTree(n int, edges [][]int) bool {
    obj := NewUF(n)
    for _, v := range edges {
        if obj.Connected(v[0], v[1]) { // 如何判断没有环,就是判断有没有连通,比如一开始有[0 1] [0 2], 此时如果来[1 2],12已经是连通的,那就成环了。
            return false
        }
        obj.Union(v[0], v[1])
    }
    return obj.count==1
}

734. 句子相似性(会员/简单)

我们可以将一个句子表示为一个单词数组,例如,句子 "I am happy with leetcode" 可以表示为 arr = ["I","am",happy","with","leetcode"]. 给定两个句子 sentence1 和 sentence2 分别表示为一个字符串数组,并给定一个字符串对 similarPairs ,其中 similarPairs[i] = [xi, yi] 表示两个单词 xi and yi 是相似的。如果 sentence1 和 sentence2 相似则返回 true ,如果不相似则返回 false 。
两个句子是相似的,如果:它们具有 相同的长度 (即相同的字数)sentence1[i] 和 sentence2[i] 是相似的. 请注意,一个词总是与它自己相似,也请注意,相似关系是不可传递的。例如,如果单词 a 和 b 是相似的,单词 b 和 c 也是相似的,那么 a 和 c  不一定相似 。

输入: sentence1 = ["great","acting","skills"], sentence2 = ["fine","drama","talent"], similarPairs = [["great","fine"],["drama","acting"],["skills","talent"]]
输出: true
解释: 这两个句子长度相同,每个单词都相似。

输入: sentence1 = ["great"], sentence2 = ["great"], similarPairs = []
输出: true
解释: 一个单词和它本身相似。

输入: sentence1 = ["great"], sentence2 = ["doubleplus","good"], similarPairs = [["great","doubleplus"]]
输出: false
解释: 因为它们长度不同,所以返回false。
// 此题需要注意,不是一对一的关系,是多对多,比如:["brunch","meal"],["breakfast","meal"],["food","meal"],多对多,其实就不方便用map[string]string{}了。最简单的场景,没用到任何算法。
func areSentencesSimilar(sentence1 []string, sentence2 []string, similarPairs [][]string) bool {
    if len(sentence1) != len(sentence2){
        return false
    }
    wordSet := map[string]int{}
    for _, v := range similarPairs{
        str := v[0]+"#"+v[1]  // 中间加一个#,真的是很机智了。
        wordSet[str]++
    }

    for i, v1 := range sentence1{
        v2 :=sentence2[i]
        if v1 != v2 && wordSet[v1+"#"+v2] == 0 && wordSet[v2+"#"+v1] == 0 {
            return false
        }
    }

    return true
}

737. 句子相似性 II(会员/中等)

我们可以将一个句子表示为一个单词数组,例如,句子 I am happy with leetcode"可以表示为 arr = ["I","am",happy","with","leetcode"]

给定两个句子 sentence1 和 sentence2 分别表示为一个字符串数组,并给定一个字符串对 similarPairs ,其中 similarPairs[i] = [xi, yi] 表示两个单词 xi 和 yi 是相似的。

如果 sentence1 和 sentence2 相似则返回 true ,如果不相似则返回 false 。

两个句子是相似的,如果:

它们具有 相同的长度 (即相同的字数)
sentence1[i] 和 sentence2[i] 是相似的
请注意,一个词总是与它自己相似,也请注意,相似关系是可传递的。例如,如果单词 a 和 b 是相似的,单词 b 和 c 也是相似的,那么 a 和 c 也是 相似 的。

输入: sentence1 = ["great","acting","skills"], sentence2 = ["fine","drama","talent"], similarPairs = [["great","good"],["fine","good"],["drama","acting"],["skills","talent"]]
输出: true
解释: 这两个句子长度相同,每个单词都相似。

输入: sentence1 = ["I","love","leetcode"], sentence2 = ["I","love","onepiece"], similarPairs = [["manga","onepiece"],["platform","anime"],["leetcode","platform"],["anime","manga"]]
输出: true
解释: "leetcode" --> "platform" --> "anime" --> "manga" --> "onepiece".
因为“leetcode”和“onepiece”相似,而且前两个单词是相同的,所以这两句话是相似的。

输入: sentence1 = ["I","love","leetcode"], sentence2 = ["I","love","onepiece"], similarPairs = [["manga","hunterXhunter"],["platform","anime"],["leetcode","platform"],["anime","manga"]]
输出: false
解释: “leetcode”和“onepiece”不相似。

func main() {
    sentence1 := []string{"great", "acting", "skills"}
    sentence2 := []string{"fine", "drama", "talent"}
    similarPairs := [][]string{{"great", "fine"}, {"drama", "acting"}, {"skills", "talent"}}
    fmt.Println(areSentencesSimilarTwo(sentence1, sentence2, similarPairs))

    sentence11 := []string{"I", "love", "leetcode"}
    sentence22 := []string{"I", "love", "onepiece"}
    similarPairs2 := [][]string{{"manga", "onepiece"}, {"platform", "anime"}, {"leetcode", "platform"}, {"anime", "manga"}}
    fmt.Println(areSentencesSimilarTwo(sentence11, sentence22, similarPairs2))
}

func areSentencesSimilarTwo(sentence1 []string, sentence2 []string, similarPairs [][]string) bool {
    sen1Len, sen2Len := len(sentence1), len(sentence2)
    if sen1Len != sen2Len {
        return false
    }
    obj := NewUF(sen1Len, sentence1, sentence2)

    for _, v := range similarPairs {
        obj.Union(v[0], v[1])
    }

    flag := true

    //word1 和 word2一一对应查并查集, 如果有一个不是相似的,则flag = false
    for i := 0; i < sen1Len; i++ {
        if obj.Find(sentence1[i]) != obj.Find(sentence2[i]) {
            flag = false
        }
    }
    return flag
}

// ======================= 以下这个模板也比较经典,是和string有关的(注意要记住)  =========================
type UF struct {
    count  int
    parent map[string]string
}

func NewUF(x int, sentence1 []string, sentence2 []string) *UF {
    u := UF{
        count:  x,
        parent: make(map[string]string, x),  //这个是核心
    }
    for i := 0; i < x; i++ {
        u.parent[sentence1[i]] = sentence1[i]
        u.parent[sentence2[i]] = sentence2[i]
    }
    return &u
}

func (u *UF) Find(x string) string {
    if strings.Compare(u.parent[x], x) != 0 { // u.parent[x]==x,strings.Compare结果=0
        u.parent[x] = u.Find(u.parent[x])
    }
    return u.parent[x]
}

func (u *UF) Union(a, b string) {
    rootA, rootB := u.Find(a), u.Find(b)
    if rootA == rootB {
        return
    }
    u.parent[rootA] = rootB
    u.count--
}

func (u *UF) Connected(a, b string) bool {
    return u.Find(a) == u.Find(b)
}

934. 最短的桥(中等)

给你一个大小为 n x n 的二元矩阵 grid ,其中 1 表示陆地,0 表示水域。岛 是由四面相连的 1 形成的一个最大组,即不会与非组内的任何其他 1 相连。grid 中 恰好存在两座岛 。你可以将任意数量的 0 变为 1 ,以使两座岛连接起来,变成 一座岛 。返回必须翻转的 0 的最小数目。

输入:grid = [[0,1],[1,0]]
输出:1

输入:grid = [[0,1,0],[0,0,0],[0,0,1]]
输出:2

输入:grid = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1

827. 最大人工岛(困难)

给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。返回执行此操作后,grid 中最大的岛屿面积是多少?岛屿 由一组上、下、左、右四个方向相连的 1 形成。

输入: grid = [[1, 0], [0, 1]]
输出: 3
解释: 将一格0变成1,最终连通两个小岛得到面积为 3 的岛屿。

输入: grid = [[1, 1], [1, 0]]
输出: 4
解释: 将一格0变成1,岛屿的面积扩大为 4。

输入: grid = [[1, 1], [1, 1]]
输出: 4
解释: 没有0可以让我们变成1,面积依然为 4。

684. 冗余连接(中等)

树可以看成是一个连通且 无环 的 无向 图。给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。

输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]

输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]

463. 岛屿的周长(简单)

给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边

694. 不同岛屿的数量(会员/中等/有点难)

给定一个非空 01 二维数组表示的网格,一个岛屿由四连通(上、下、左、右四个方向)的 1 组成,你可以认为网格的四周被海水包围。请你计算这个网格中共有多少个形状不同的岛屿。两个岛屿被认为是相同的,当且仅当一个岛屿可以通过平移变换(不可以旋转、翻转)和另一个岛屿重合。

输入: grid = [[1,1,0,0,0],[1,1,0,0,0],[0,0,0,1,1],[0,0,0,1,1]]
输出:1

输入: grid = [[1,1,0,1,1],[1,0,0,0,0],[0,0,0,0,1],[1,1,0,1,1]]
输出: 3

[图片上传失败...(image-40f795-1671479296836)]

924. 尽量减少恶意软件的传播(困难)

给出了一个由 n 个节点组成的网络,用 n × n 个邻接矩阵图 graph 表示。在节点网络中,当 graph[i][j] = 1 时,表示节点 i 能够直接连接到另一个节点 j。 一些节点 initial 最初被恶意软件感染。只要两个节点直接连接,且其中至少一个节点受到恶意软件的感染,那么两个节点都将被恶意软件感染。这种恶意软件的传播将继续,直到没有更多的节点可以被这种方式感染。假设 M(initial) 是在恶意软件停止传播之后,整个网络中感染恶意软件的最终节点数。如果从 initial 中移除某一节点能够最小化 M(initial), 返回该节点。如果有多个节点满足条件,就返回索引最小的节点。请注意,如果某个节点已从受感染节点的列表 initial 中删除,它以后仍有可能因恶意软件传播而受到感染。

输入:graph = [[1,1,0],[1,1,0],[0,0,1]], initial = [0,1]
输出:0

输入:graph = [[1,0,0],[0,1,0],[0,0,1]], initial = [0,2]
输出:0

输入:graph = [[1,1,1],[1,1,1],[1,1,1]], initial = [1,2]
输出:1
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容