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

[TOC]

基礎(chǔ)知識(shí)

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

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

B站上古城算法講的也很不錯(cuò);

并查集是一種樹形的數(shù)據(jù)結(jié)構(gòu),用于處理不交集的合并(union)和查詢(find)問題

Find:確認(rèn)元素屬于哪一個(gè)子集,確認(rèn)兩個(gè)元素是不是同一個(gè)子集

Union:將兩個(gè)子集合并為同一個(gè)集合

找到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的父母,都可以,合并兩個(gè)子集。

基礎(chǔ)模板偽代碼

初始化

func makeSet(x)
  x.parent=x

查詢

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

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

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

基礎(chǔ)模板

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--
}

// (可選)判斷兩個(gè)節(jié)點(diǎn)有沒有連接
func (u *UF) Connected(a, b int) bool {
    return u.Find(a) == u.Find(b)
}

//(可選),其實(shí)一般都直接是u.count
func (u *UF) Count() int {
    return u.count
}

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

weight優(yōu)化模板

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]
}

// 合并優(yōu)化版,利用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--
}

//(可選)判斷兩個(gè)節(jié)點(diǎn)有沒有連接
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優(yōu)化模板

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]
}

 
// 合并優(yōu)化版,利用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相等的情況下合并后才需要維護(hù)rank,rank需要+1
        u.parent[rootA] = rootB
        u.rank[rootB]++
    }
    u.count--
}


// (可選)判斷兩個(gè)節(jié)點(diǎn)有沒有連接
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題)


// ======================= 以下這個(gè)模板也比較經(jīng)典,是和string有關(guān)的(注意要記?。? =========================
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),  //這個(gè)是核心
    }
    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結(jié)果=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. 無向圖中連通分量的數(shù)目(會(huì)員/中等)

你有一個(gè)包含 n 個(gè)節(jié)點(diǎn)的圖。給定一個(gè)整數(shù) n 和一個(gè)數(shù)組 edges ,其中 edges[i] = [ai, bi] 表示圖中 ai 和 bi 之間有一條邊。
返回 圖中已連接分量的數(shù)目 。
輸入: 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. 省份數(shù)量(中等)

有 n 個(gè)城市,其中一些彼此相連,另一些沒有相連。如果城市 a 與城市 b 直接相連,且城市 b 與城市 c 直接相連,那么城市 a 與城市 c 間接相連。省份 是一組直接或間接相連的城市,組內(nèi)不含其他沒有相連的城市。
給你一個(gè) n x n 的矩陣 isConnected ,其中 isConnected[i][j] = 1 表示第 i 個(gè)城市和第 j 個(gè)城市直接相連,而 isConnected[i][j] = 0 表示二者不直接相連。返回矩陣中 省份 的數(shù)量。

輸入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
輸出:2
// 這一題很明顯需要用并查集來做,而不是利用200題島嶼數(shù)量的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. 島嶼數(shù)量 II(會(huì)員/困難)

給你一個(gè)大小為 m x n 的二進(jìn)制網(wǎng)格 grid 。網(wǎng)格表示一個(gè)地圖,其中,0 表示水,1 表示陸地。最初,grid 中的所有單元格都是水單元格(即,所有單元格都是 0)??梢酝ㄟ^執(zhí)行 addLand 操作,將某個(gè)位置的水轉(zhuǎn)換成陸地。給你一個(gè)數(shù)組 positions ,其中 positions[i] = [ri, ci] 是要執(zhí)行第 i 次操作的位置 (ri, ci) 。返回一個(gè)整數(shù)數(shù)組 answer ,其中 answer[i] 是將單元格 (ri, ci) 轉(zhuǎn)換為陸地后,地圖中島嶼的數(shù)量。島嶼 的定義是被「水」包圍的「陸地」,通過水平方向或者垂直方向上相鄰的陸地連接而成。你可以假設(shè)地圖網(wǎng)格的四邊均被無邊無際的「水」所包圍。

輸入:m = 3, n = 3, positions = [[0,0],[0,1],[1,2],[2,1]]
輸出:[1,1,2,3]
解釋:
起初,二維網(wǎng)格 grid 被全部注入「水」。(0 代表「水」,1 代表「陸地」)
- 操作 #1:addLand(0, 0) 將 grid[0][0] 的水變?yōu)殛懙?。此時(shí)存在 1 個(gè)島嶼。
- 操作 #2:addLand(0, 1) 將 grid[0][1] 的水變?yōu)殛懙?。此時(shí)存在 1 個(gè)島嶼。
- 操作 #3:addLand(1, 2) 將 grid[1][2] 的水變?yōu)殛懙?。此時(shí)存在 2 個(gè)島嶼。
- 操作 #4:addLand(2, 1) 將 grid[2][1] 的水變?yōu)殛懙?。此時(shí)存在 3 個(gè)島嶼。

[圖片上傳失敗...(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) // 構(gòu)造函數(shù)
    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) 這個(gè)點(diǎn)。

        // 這一段用來干嘛的,主要是判斷輸入有沒有重復(fù)的,重復(fù)的可以直接返回結(jié)果,比如 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) { // 如果其中一個(gè)方向有已經(jīng)填過的島嶼,且沒有連接在一起,那么就可以將填海位置與已經(jīng)填過的島嶼連接在一起,同時(shí)孤島數(shù)量-1(在Union里面自動(dòng)做掉)
                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)            // 構(gòu)造函數(shù)
    visited := make([]bool, m*n) // 二維變一維,這里最精髓的地方就是二維問題轉(zhuǎ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) 這個(gè)點(diǎn)。

        // 這一段用來干嘛的,主要是判斷輸入有沒有重復(fù)的,重復(fù)的可以直接返回結(jié)果,比如 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) { // 如果其中一個(gè)方向有已經(jīng)填過的島嶼,且沒有連接在一起,那么就可以將填海位置與已經(jīng)填過的島嶼連接在一起,同時(shí)孤島數(shù)量-1(在Union里面自動(dòng)做掉)
                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]
}

// 合并優(yōu)化版,利用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--
}

// 判斷兩個(gè)節(jié)點(diǎn)有沒有連接
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. 彼此熟識(shí)的最早時(shí)間(會(huì)員/中等)

在一個(gè)社交圈子當(dāng)中,有 n 個(gè)人。每個(gè)人都有一個(gè)從 0 到 n - 1 的唯一編號(hào)。我們有一份日志列表 logs,其中 logs[i] = [timestampi, xi, yi] 表示 xi 和 yi 將在同一時(shí)間 timestampi 成為朋友。友誼是相互的。也就是說,如果 a 和 b 是朋友,那么 b 和 a 也是朋友。同樣,如果 a 和 b 是朋友,或者 a 是 b 朋友的朋友 ,那么 a 和 b 是熟識(shí)友。返回圈子里所有人之間都熟識(shí)的最早時(shí)間。如果找不到最早時(shí)間,就返回 -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
解釋:
第一次結(jié)交發(fā)生在 timestamp = 20190101,0 和 1 成為好友,社交朋友圈如下 [0,1], [2], [3], [4], [5]。
第二次結(jié)交發(fā)生在 timestamp = 20190104,3 和 4 成為好友,社交朋友圈如下 [0,1], [2], [3,4], [5].
第三次結(jié)交發(fā)生在 timestamp = 20190107,2 和 3 成為好友,社交朋友圈如下 [0,1], [2,3,4], [5].
第四次結(jié)交發(fā)生在 timestamp = 20190211,1 和 5 成為好友,社交朋友圈如下 [0,1,5], [2,3,4].
第五次結(jié)交發(fā)生在 timestamp = 20190224,2 和 4 已經(jīng)是好友了。
第六次結(jié)交發(fā)生在 timestamp = 20190301,0 和 3 成為好友,大家都互相熟識(shí)了。

輸入: 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 { // 這里按照第一列進(jìn)行排序非常重要,因?yàn)椴灰欢〞r(shí)間先的放在數(shù)組前面了
        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. 最低成本聯(lián)通所有城市(會(huì)員/中等)

想象一下你是個(gè)城市基建規(guī)劃者,地圖上有 n 座城市,它們按以 1 到 n 的次序編號(hào)。給你整數(shù) n 和一個(gè)數(shù)組 conections,其中 connections[i] = [xi, yi, costi] 表示將城市 xi 和城市 yi 連接所要的costi(連接是雙向的)。返回連接所有城市的最低成本,每對(duì)城市之間至少有一條路徑。如果無法連接所有 n 個(gè)城市,返回 -1. 該 最小成本 應(yīng)該是所用全部連接成本的總和。

輸入: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開始的,也就是三個(gè)城市,1,2,3,必須要+1,避免panic.
    sort.Slice(connections, func(i, j int) bool { // 這里按照第一列進(jìn)行排序非常重要,因?yàn)椴灰欢〞r(shí)間先的放在數(shù)組前面了
        return connections[i][2] < connections[j][2]
    })

    fmt.Println(connections)
    res := 0
    for _, v := range connections {
        if !obj.Connected(v[0], v[1]) {  // 其實(shí)這里還有一點(diǎn)貪心算法的意思,如果不連,就連通。不能無腦連通,有可能之前已經(jīng)是連通狀態(tài)了
            obj.Union(v[0], v[1])
            res += v[2]
        }

        if obj.count == 2 { // 這里需要注意的是城市是從1開始的,也就是三個(gè)城市,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. 按字典序排列最小的等效字符串(會(huì)員/中等)

給出長(zhǎng)度相同的兩個(gè)字符串s1 和 s2 ,還有一個(gè)字符串 baseStr 。其中  s1[i] 和 s2[i]  是一組等價(jià)字符。舉個(gè)例子,如果 s1 = "abc" 且 s2 = "cde",那么就有 'a' == 'c', 'b' == 'd', 'c' == 'e'。
等價(jià)字符遵循任何等價(jià)關(guān)系的一般規(guī)則:
自反性 :'a' == 'a'
對(duì)稱性 :'a' == 'b' 則必定有 'b' == 'a'
傳遞性 :'a' == 'b' 且 'b' == 'c' 就表明 'a' == 'c'
例如, s1 = "abc" 和 s2 = "cde" 的等價(jià)信息和之前的例子一樣,那么 baseStr = "eed" , "acd" 或 "aab",這三個(gè)字符串都是等價(jià)的,而 "aab" 是 baseStr 的按字典序最小的等價(jià)字符串. 利用 s1 和 s2 的等價(jià)信息,找出并返回 baseStr 的按字典序排列最小的等價(jià)字符串。

輸入:s1 = "parker", s2 = "morris", baseStr = "parser"
輸出:"makkek"
解釋:根據(jù) A 和 B 中的等價(jià)信息,我們可以將這些字符分為 [m,p], [a,o], [k,r,s], [e,i] 共 4 組。每組中的字符都是等價(jià)的,并按字典序排列。所以答案是 "makkek"。

輸入:s1 = "hello", s2 = "world", baseStr = "hold"
輸出:"hdld"
解釋:根據(jù) A 和 B 中的等價(jià)信息,我們可以將這些字符分為 [h,w], [d,e,o], [l,r] 共 3 組。所以只有 S 中的第二個(gè)字符 'o' 變成 'd',最后答案為 "hdld"。

輸入:s1 = "leetcode", s2 = "programs", baseStr = "sourcecode"
輸出:"aauaaaaada"
解釋:我們可以把 A 和 B 中的等價(jià)字符分為 [a,o,e,r,s,c], [l,p], [g,t] 和 [d,m] 共 4 組,因此 S 中除了 'u' 和 'd' 之外的所有字母都轉(zhuǎn)化成了 'a',最后答案為 "aauaaaaada"。
func main() {
    s1 := "parker"
    s2 := "morris"
    baseStr := "parser"
    fmt.Println(smallestEquivalentString(s1, s2, baseStr))
}
// 這個(gè)思路有點(diǎn)絕啊,將字符轉(zhuǎn)化為數(shù)字
func smallestEquivalentString(s1 string, s2 string, baseStr string) string {
    s1Len := len(s1)
    obj := NewUF(26) // 這里非常關(guān)鍵,為什么是26呢。因?yàn)槭亲帜?,最?6個(gè)需要找父節(jié)點(diǎn)
    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]
}

// 此題在這里最基礎(chǔ)的模板針對(duì)題目進(jìn)行了優(yōu)化,也就是要找最小的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. 得分最高的路徑(會(huì)員/中等)

給定一個(gè) m x n 的整數(shù)矩陣 grid,返回從 (0,0) 開始到 (m - 1, n - 1) 在四個(gè)基本方向上移動(dòng)的路徑的最大 分?jǐn)?shù) 。一條路徑的 分?jǐn)?shù) 是該路徑上的最小值。例如,路徑 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
著作權(quán)歸領(lǐng)扣網(wǎng)絡(luò)所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系官方授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

[圖片上傳失敗...(image-388dda-1671479296836)]


// ================== 解法一: 并查集  =====================
// 這里其實(shí)很關(guān)鍵,就相當(dāng)于從大到小挨個(gè)中,后來看是不是能連通
// 1. 把節(jié)點(diǎn)值 從大到小 逐個(gè)嘗試,直到最后拼出來grid[0,0]到grid[row-1, col-1](從左上角到右下角)的通路。
// 2. 進(jìn)行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 { // 按照二維數(shù)組的值從大到小排列
        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
}

====================  上述是利用數(shù)組的,也可以轉(zhuǎn)化為node結(jié)構(gòu)體來   ====================
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 { // 按照二維數(shù)組的值從大到小排列
        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. 以圖判樹(會(huì)員/中等)

給定編號(hào)從 0 到 n - 1 的 n 個(gè)結(jié)點(diǎn)。給定一個(gè)整數(shù) n 和一個(gè) edges 列表,其中 edges[i] = [ai, bi] 表示圖中節(jié)點(diǎn) ai 和 bi 之間存在一條無向邊。如果這些邊能夠形成一個(gè)合法有效的樹結(jié)構(gòu),則返回 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形成了一個(gè)環(huán),所以會(huì)掛。
    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]) { // 如何判斷沒有環(huán),就是判斷有沒有連通,比如一開始有[0 1] [0 2], 此時(shí)如果來[1 2],12已經(jīng)是連通的,那就成環(huán)了。
            return false
        }
        obj.Union(v[0], v[1])
    }
    return obj.count==1
}

734. 句子相似性(會(huì)員/簡(jiǎn)單)

我們可以將一個(gè)句子表示為一個(gè)單詞數(shù)組,例如,句子 "I am happy with leetcode" 可以表示為 arr = ["I","am",happy","with","leetcode"]. 給定兩個(gè)句子 sentence1 和 sentence2 分別表示為一個(gè)字符串?dāng)?shù)組,并給定一個(gè)字符串對(duì) similarPairs ,其中 similarPairs[i] = [xi, yi] 表示兩個(gè)單詞 xi and yi 是相似的。如果 sentence1 和 sentence2 相似則返回 true ,如果不相似則返回 false 。
兩個(gè)句子是相似的,如果:它們具有 相同的長(zhǎng)度 (即相同的字?jǐn)?shù))sentence1[i] 和 sentence2[i] 是相似的. 請(qǐng)注意,一個(gè)詞總是與它自己相似,也請(qǐng)注意,相似關(guān)系是不可傳遞的。例如,如果單詞 a 和 b 是相似的,單詞 b 和 c 也是相似的,那么 a 和 c  不一定相似 。

輸入: sentence1 = ["great","acting","skills"], sentence2 = ["fine","drama","talent"], similarPairs = [["great","fine"],["drama","acting"],["skills","talent"]]
輸出: true
解釋: 這兩個(gè)句子長(zhǎng)度相同,每個(gè)單詞都相似。

輸入: sentence1 = ["great"], sentence2 = ["great"], similarPairs = []
輸出: true
解釋: 一個(gè)單詞和它本身相似。

輸入: sentence1 = ["great"], sentence2 = ["doubleplus","good"], similarPairs = [["great","doubleplus"]]
輸出: false
解釋: 因?yàn)樗鼈冮L(zhǎng)度不同,所以返回false。
// 此題需要注意,不是一對(duì)一的關(guān)系,是多對(duì)多,比如:["brunch","meal"],["breakfast","meal"],["food","meal"],多對(duì)多,其實(shí)就不方便用map[string]string{}了。最簡(jiǎn)單的場(chǎng)景,沒用到任何算法。
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]  // 中間加一個(gè)#,真的是很機(jī)智了。
        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(會(huì)員/中等)

我們可以將一個(gè)句子表示為一個(gè)單詞數(shù)組,例如,句子 I am happy with leetcode"可以表示為 arr = ["I","am",happy","with","leetcode"]

給定兩個(gè)句子 sentence1 和 sentence2 分別表示為一個(gè)字符串?dāng)?shù)組,并給定一個(gè)字符串對(duì) similarPairs ,其中 similarPairs[i] = [xi, yi] 表示兩個(gè)單詞 xi 和 yi 是相似的。

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

兩個(gè)句子是相似的,如果:

它們具有 相同的長(zhǎng)度 (即相同的字?jǐn)?shù))
sentence1[i] 和 sentence2[i] 是相似的
請(qǐng)注意,一個(gè)詞總是與它自己相似,也請(qǐng)注意,相似關(guān)系是可傳遞的。例如,如果單詞 a 和 b 是相似的,單詞 b 和 c 也是相似的,那么 a 和 c 也是 相似 的。

輸入: sentence1 = ["great","acting","skills"], sentence2 = ["fine","drama","talent"], similarPairs = [["great","good"],["fine","good"],["drama","acting"],["skills","talent"]]
輸出: true
解釋: 這兩個(gè)句子長(zhǎng)度相同,每個(gè)單詞都相似。

輸入: sentence1 = ["I","love","leetcode"], sentence2 = ["I","love","onepiece"], similarPairs = [["manga","onepiece"],["platform","anime"],["leetcode","platform"],["anime","manga"]]
輸出: true
解釋: "leetcode" --> "platform" --> "anime" --> "manga" --> "onepiece".
因?yàn)椤發(fā)eetcode”和“onepiece”相似,而且前兩個(gè)單詞是相同的,所以這兩句話是相似的。

輸入: sentence1 = ["I","love","leetcode"], sentence2 = ["I","love","onepiece"], similarPairs = [["manga","hunterXhunter"],["platform","anime"],["leetcode","platform"],["anime","manga"]]
輸出: false
解釋: “l(fā)eetcode”和“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一一對(duì)應(yīng)查并查集, 如果有一個(gè)不是相似的,則flag = false
    for i := 0; i < sen1Len; i++ {
        if obj.Find(sentence1[i]) != obj.Find(sentence2[i]) {
            flag = false
        }
    }
    return flag
}

// ======================= 以下這個(gè)模板也比較經(jīng)典,是和string有關(guān)的(注意要記?。? =========================
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),  //這個(gè)是核心
    }
    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結(jié)果=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. 最短的橋(中等)

給你一個(gè)大小為 n x n 的二元矩陣 grid ,其中 1 表示陸地,0 表示水域。島 是由四面相連的 1 形成的一個(gè)最大組,即不會(huì)與非組內(nèi)的任何其他 1 相連。grid 中 恰好存在兩座島 。你可以將任意數(shù)量的 0 變?yōu)?1 ,以使兩座島連接起來,變成 一座島 。返回必須翻轉(zhuǎn)的 0 的最小數(shù)目。

輸入: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. 最大人工島(困難)

給你一個(gè)大小為 n x n 二進(jìn)制矩陣 grid 。最多 只能將一格 0 變成 1 。返回執(zhí)行此操作后,grid 中最大的島嶼面積是多少?島嶼 由一組上、下、左、右四個(gè)方向相連的 1 形成。

輸入: grid = [[1, 0], [0, 1]]
輸出: 3
解釋: 將一格0變成1,最終連通兩個(gè)小島得到面積為 3 的島嶼。

輸入: grid = [[1, 1], [1, 0]]
輸出: 4
解釋: 將一格0變成1,島嶼的面積擴(kuò)大為 4。

輸入: grid = [[1, 1], [1, 1]]
輸出: 4
解釋: 沒有0可以讓我們變成1,面積依然為 4。

684. 冗余連接(中等)

樹可以看成是一個(gè)連通且 無環(huán) 的 無向 圖。給定往一棵 n 個(gè)節(jié)點(diǎn) (節(jié)點(diǎn)值 1~n) 的樹中添加一條邊后的圖。添加的邊的兩個(gè)頂點(diǎn)包含在 1 到 n 中間,且這條附加的邊不屬于樹中已存在的邊。圖的信息記錄于長(zhǎng)度為 n 的二維數(shù)組 edges ,edges[i] = [ai, bi] 表示圖中在 ai 和 bi 之間存在一條邊。請(qǐng)找出一條可以刪去的邊,刪除后可使得剩余部分是一個(gè)有著 n 個(gè)節(jié)點(diǎn)的樹。如果有多個(gè)答案,則返回?cái)?shù)組 edges 中最后出現(xiàn)的邊。

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

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

463. 島嶼的周長(zhǎng)(簡(jiǎn)單)

給定一個(gè) row x col 的二維網(wǎng)格地圖 grid ,其中:grid[i][j] = 1 表示陸地, grid[i][j] = 0 表示水域。網(wǎng)格中的格子 水平和垂直 方向相連(對(duì)角線方向不相連)。整個(gè)網(wǎng)格被水完全包圍,但其中恰好有一個(gè)島嶼(或者說,一個(gè)或多個(gè)表示陸地的格子相連組成的島嶼)。島嶼中沒有“湖”(“湖” 指水域在島嶼內(nèi)部且不和島嶼周圍的水相連)。格子是邊長(zhǎng)為 1 的正方形。網(wǎng)格為長(zhǎng)方形,且寬度和高度均不超過 100 。計(jì)算這個(gè)島嶼的周長(zhǎng)。

輸入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
輸出:16
解釋:它的周長(zhǎng)是上面圖片中的 16 個(gè)黃色的邊

694. 不同島嶼的數(shù)量(會(huì)員/中等/有點(diǎn)難)

給定一個(gè)非空 01 二維數(shù)組表示的網(wǎng)格,一個(gè)島嶼由四連通(上、下、左、右四個(gè)方向)的 1 組成,你可以認(rèn)為網(wǎng)格的四周被海水包圍。請(qǐng)你計(jì)算這個(gè)網(wǎng)格中共有多少個(gè)形狀不同的島嶼。兩個(gè)島嶼被認(rèn)為是相同的,當(dāng)且僅當(dāng)一個(gè)島嶼可以通過平移變換(不可以旋轉(zhuǎn)、翻轉(zhuǎn))和另一個(gè)島嶼重合。

輸入: 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. 盡量減少惡意軟件的傳播(困難)

給出了一個(gè)由 n 個(gè)節(jié)點(diǎn)組成的網(wǎng)絡(luò),用 n × n 個(gè)鄰接矩陣圖 graph 表示。在節(jié)點(diǎn)網(wǎng)絡(luò)中,當(dāng) graph[i][j] = 1 時(shí),表示節(jié)點(diǎn) i 能夠直接連接到另一個(gè)節(jié)點(diǎn) j。 一些節(jié)點(diǎn) initial 最初被惡意軟件感染。只要兩個(gè)節(jié)點(diǎn)直接連接,且其中至少一個(gè)節(jié)點(diǎn)受到惡意軟件的感染,那么兩個(gè)節(jié)點(diǎn)都將被惡意軟件感染。這種惡意軟件的傳播將繼續(xù),直到?jīng)]有更多的節(jié)點(diǎn)可以被這種方式感染。假設(shè) M(initial) 是在惡意軟件停止傳播之后,整個(gè)網(wǎng)絡(luò)中感染惡意軟件的最終節(jié)點(diǎn)數(shù)。如果從 initial 中移除某一節(jié)點(diǎn)能夠最小化 M(initial), 返回該節(jié)點(diǎn)。如果有多個(gè)節(jié)點(diǎn)滿足條件,就返回索引最小的節(jié)點(diǎn)。請(qǐng)注意,如果某個(gè)節(jié)點(diǎn)已從受感染節(jié)點(diǎn)的列表 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容