第7周 字典树和并查集、高级搜索、红黑树和AVL树

2021-12-20
13分钟阅读时长

题目数量:17

第13课 | 字典树和并查集

1. Trie树的基本实现和特性

参考链接

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
// bfs
func levelOrder(root *TreeNode) (res [][]int) {
    if root == nil {
        return res
    }
    queue := []*TreeNode{root}
    for len(queue) > 0 {
        curLevel := []int{}
        for cnt := len(queue); cnt > 0; cnt-- {
            node := queue[0]
            queue = queue[1:]
            curLevel = append(curLevel, node.Val)
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        res = append(res, curLevel)
    }
    return res
}
// dfs
type Trie struct {
   child [26]*Trie
   isEnd     bool
}


func Constructor() Trie {
    return Trie{}
}


func (this *Trie) Insert(word string)  {
    root := this
    for _, w := range word {
        if root.child[w - 'a'] == nil{
            root.child[w - 'a'] = &Trie{}
        }
        root = root.child[w - 'a'] 
    }
    root.isEnd = true
}

func (this *Trie) SearchPrefix(prefix string) *Trie {
    root := this
    for _, w := range prefix {
        if root.child[w - 'a'] == nil {
            return nil
        }
        root = root.child[w - 'a']
    }
    return root
}

func (this *Trie) Search(word string) bool {
    node := this.SearchPrefix(word)
    return node != nil && node.isEnd
}


func (this *Trie) StartsWith(prefix string) bool {
    return this.SearchPrefix(prefix) != nil
}


/**
 * Your Trie object will be instantiated and called as such:
 * obj := Constructor();
 * obj.Insert(word);
 * param_2 := obj.Search(word);
 * param_3 := obj.StartsWith(prefix);
 */

2. Trie树实战题目解析:单词搜索2

实战题目

// 上面重复
var fx = [][]int{{1, 0}, {0, 1}, {-1, 0}, {0, -1}}
func findWords(board [][]byte, words []string) (res []string) {
    trie := &Trie{}
    for _, word := range words {
        trie.Insert(word)
    }
    m, n := len(board), len(board[0])
    var backtracking func (i, j int, root *Trie)
    backtracking = func(i, j int, root *Trie) {
        letter := board[i][j]
        board[i][j] = '#'
        node := root.child[letter - 'a']
        if node.word != "" {
            res = append(res, node.word)
            node.word = ""
        }
        for _, f := range fx {
            ni, nj := i + f[0], j + f[1]
            if ni < 0 || ni >= m || nj < 0 || nj >= n || board[ni][nj] == '#'{
                continue
            }
            if node.child[board[ni][nj] - 'a'] != nil {
                backtracking(ni, nj, node)
            }
        }
        board[i][j] = letter
    }
    for i := 0; i < m; i++ {
        for j := 0; j < n; j++ {
            if trie.StartsWith(string(board[i][j])) {
                backtracking(i, j, trie)
            }
        }
    }
    return res
}

type Trie struct {
   child [26]*Trie
   word     string
}


func Constructor() Trie {
    return Trie{}
}


func (this *Trie) Insert(word string)  {
    root := this
    for _, w := range word {
        if root.child[w - 'a'] == nil{
            root.child[w - 'a'] = &Trie{}
        }
        root = root.child[w - 'a'] 
    }
    root.word = word
}

func (this *Trie) SearchPrefix(prefix string) *Trie {
    root := this
    for _, w := range prefix {
        if root.child[w - 'a'] == nil {
            return nil
        }
        root = root.child[w - 'a']
    }
    return root
}

func (this *Trie) Search(word string) bool {
    node := this.SearchPrefix(word)
    return node != nil && node.word != ""
}


func (this *Trie) StartsWith(prefix string) bool {
    return this.SearchPrefix(prefix) != nil
}
  • 分析单词搜索 II 用 Tire 树方式实现的时间复杂度,请同学们提交在第 7 周的学习总结中。

3. 并查集的基本实现、特性和实战题目解析

参考链接

// dfs、bfs、并查集 见下面实战部分题解
type UF struct {
    Parent []int
    Count int
}
func NewUF(count int) *UF {
    parent := make([]int, count)
    for i := range parent {
        parent[i] = i
    }
    return &UF{
        Parent: parent,
        Count: count,
    }
}
func (u *UF) Find(p int) int {
    parent := u.Parent
    for p != parent[p] {
        // a <- b <- p 改 为 a <- b , a <- p // 路径压缩
        parent[p] = parent[parent[p]]
        p = parent[p]
    }
    return p
}
func (u *UF) Union(p, q int) {
    rootP := u.Find(p)
    rootQ := u.Find(q)
    if rootP != rootQ {
        u.Parent[rootP] = rootQ
        u.Count--
    }
}

实战题目

// 并查集
type UF struct {
    parent []int
    count int
}
func NewUF(count int) *UF {
    parent := make([]int, count)
    for i := range parent {
        parent[i] = i
    }
    return &UF{
        parent: parent,
        count: count,
    }
}

func (u *UF) find(p int) int {
    parent := u.parent
    for p != parent[p] {
        parent[p] = parent[parent[p]]
        p = parent[p]
    }
    return p
}

func (u *UF) union(p, q int) {
    rootP := u.find(p)
    rootQ := u.find(q)
    if rootP != rootQ {
        u.parent[rootP] = rootQ
        u.count--
    }
}

func findCircleNum(M [][]int) int {
    m := len(M)
    u := NewUF(m)
    for i := 0; i < m; i++ {
        for j := 0; j < m; j++ {
            if M[i][j] == 1 && i != j {
                u.union(i, j)
            }
        }
    }
    return u.count
}
// dfs
// bfs
// 并查集
func numIslands(grid [][]byte) int {
    m, n := len(grid), len(grid[0])
    u := NewUF(grid)
    dx := []int{1, 0, -1, 0}
    dy := []int{0, 1, 0, -1}
    for i := 0; i < m; i++ {
        for j := 0; j < n; j++ {
            if grid[i][j] == '1' {
                grid[i][j] = '0'
                for k := 0; k < 4; k++ {
                    x, y := i + dx[k], j + dy[k]
                    if x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1' {
                        u.Union(i * n + j, x * n + y)
                    }
                }
            }
        }
    }
    return u.Count()
}

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

func NewUF(grid [][]byte) *UF {
    m, n := len(grid), len(grid[0])
    parent := make([]int, m * n)
    count := 0
    for i := 0; i < m; i++ {
        for j := 0; j < n; j++ {
            idx := i * n + j
            if grid[i][j] == '1' {
                count++
                parent[idx] = idx
            } else {
                parent[idx] = -1
            }
        }
    }
    return &UF{
        parent: parent,
        rank: make([]int, len(parent)),
        count: count,
    }
}

func (u *UF) Find(p int) int {
    parent := u.parent
    for p != parent[p] {
        parent[p] = parent[parent[p]]
        p = parent[p]
    }
    return p
}

func (u *UF) Union(x, y int) {
    rootX, rootY := u.Find(x), u.Find(y)
    if rootX != rootY {
        if u.rank[rootX] < u.rank[rootY] {
            u.parent[rootX] = rootY
        } else if u.rank[rootX] > u.rank[rootY]  {
            u.parent[rootY] = rootX
        } else {
            u.parent[rootY] = rootX
            u.rank[rootX] += 1
        }
        u.count--
    }
}

func (u *UF) Count() int {
    return u.count
}
type Uf struct {
	parent []int
}

func NewUf(n int) *Uf {
	parent := make([]int, n)
	for i := range parent {
		parent[i] = i
	}
	return &Uf{parent: parent}
}

func (u *Uf) find(p int) int {
	parent := u.parent
	for p != parent[p] {
		parent[p] = parent[parent[p]]
		p = parent[p]
	}
	return p
}

func (u *Uf) Union(p, q int) {
	rootP, rootQ := u.find(p), u.find(q)
	if rootP != rootQ {
		u.parent[rootP] = rootQ
	}
}

func (u *Uf) IsConnect(p, q int) bool {
	return u.find(p) == u.find(q)
}

func solve(board [][]byte) {
	m, n := len(board), len(board[0])
	u := NewUf(m*n + 2)
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if (i == 0 || i == m-1 || j == 0 || j == n-1) && board[i][j] == 'O' {
				u.Union(i*n+j, m*n)
			} else if board[i][j] == 'O' {
				dx := []int{1, 0, -1, 0}
				dy := []int{0, 1, 0, -1}
				for k := 0; k < 4; k++ {
					x, y := i+dx[k], j+dy[k]
					if board[x][y] == 'O' {
						u.Union(i*n+j, x*n+y)
					}
				}
			}
		}
	}
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if !u.IsConnect(i*n+j, m*n) && board[i][j] == 'O' {
				board[i][j] = 'X'
			}
		}
	}
}

第14课|高级搜索

1. 剪枝的实现和特性

参考链接

2. 剪枝实战题目解析:数独

实战题目

// 动态规划
func climbStairs(n int) int {
	i, j, k := 0, 1, 1
	for m := 1; m <=n; m++ {
		k = i + j
        i, j = j, k
	}
	return k
}
// dfs 记录中间值进行剪枝
func climbStairs(n int) int {
	mp := map[int]int{}
	var climb func(i int) int
	climb = func(i int) int {
		if i <= 2 {
			return i
		}
		if _, ok := mp[i]; !ok {
			mp[i] = climb(i-1) + climb(i-2)
		}
		return mp[i]
	}
	return climb(n)
}
// dfs
func generateParenthesis(n int) (res []string) {
	var generate func(l, r int, path string)
	generate = func(l, r int, path string) {
		if l == n && r == n {
			res = append(res, path)
			return
		}
		if l < n {
			generate(l+1, r, path+"(")
		}
		if r < l {
			generate(l, r+1, path+")")
		}
	}
	generate(0, 0, "")
	return res
}
// bfs, 需要结构体记录,左右括号数量和当前括号字符串
func solveNQueens(n int) (res [][]string) {
    size := 1 << n - 1
    var dfs func(int, int, int, int, []int)
    dfs = func (row, cols, pie, na int, queens []int) {
        if row == n {
            ans := make([]string, n)
            for i := 0; i < n; i++ {
                s := make([]byte, n)
                for j := 0; j < n; j++ {
                    if queens[i] == j {
                        s[j] = 'Q' 
                    } else {
                        s[j] = '.'
                    }
                }
                ans[i] = string(s)
            }
            res = append(res, ans)
            return
        }
        bit := size & (^(cols | pie | na))
        for bit > 0 {
            p := bit & -bit // 获取最后一个位1
            bit &= bit - 1 // 移除最后一个1
            queens = append(queens, bits.OnesCount(uint(p-1)))
            dfs(row + 1, cols | p, (pie | p ) << 1, (na | p) >> 1, queens)
            queens = queens[:row]
        }
    }
    dfs(0, 0, 0, 0, []int{})
    return res
}
// 自己写法
func isValidSudoku(board [][]byte) bool {
    if len(board) != 9 || len(board[0]) != 9 {
        return false
    }
    blocks, rows, cols := [9]map[byte]bool{},  [9]map[byte]bool{},  [9]map[byte]bool{}
    for i := 0; i < 9; i++ {
        blocks[i] = map[byte]bool{}
        rows[i] =  map[byte]bool{}
        cols[i] =  map[byte]bool{}
    }
    for i := 0; i < 9; i++ {
        for j := 0; j < 9; j++ {
            if board[i][j] != '.' {
                n := board[i][j]
                b := i / 3 * 3 + j / 3
                if rows[i][n] || cols[j][n] || blocks[b][n] {
                    return false
                }
                rows[i][n] = true
                cols[j][n] = true
                blocks[b][n] = true
            }
        }
    }
    return true
}
// 优化写法
func isValidSudoku(board [][]byte) bool {
    if len(board) != 9 || len(board[0]) != 9 {
        return false
    }
    blocks, rows, cols := [9][9]int{},  [9][9]int{},  [9][9]int{}
    for i := 0; i < 9; i++ {
        for j := 0; j < 9; j++ {
            if board[i][j] != '.' {
                n := board[i][j] - '1'
                b := i / 3 * 3 + j / 3
                if rows[i][n] > 0 || cols[j][n] > 0|| blocks[b][n] > 0 {
                    return false
                }
                rows[i][n]++
                cols[j][n]++
                blocks[b][n]++
            }
        }
    }
    return true
}
// 自己写法
func solveSudoku(board [][]byte) {
    blocks, rows, cols := [9]map[byte]bool{},  [9]map[byte]bool{},  [9]map[byte]bool{}
    for i := 0; i < 9; i++ {
        blocks[i] =  map[byte]bool{}
        rows[i] = map[byte]bool{}
        cols[i] =  map[byte]bool{}
    }
    emptys := [][2]int{} // 需要填写的位置
    for i := 0; i < 9; i++ {
        for j := 0; j < 9; j++ {
            n := board[i][j]
            if n == '.' {
                emptys = append(emptys, [2]int{i, j})
            } else {
                b := i / 3 * 3 + j / 3
                rows[i][n] = true
                cols[j][n] = true
                blocks[b][n] = true
            }
        }
    }
    var backtrack func(int) bool
    backtrack = func(idx int) bool {
        if idx == len(emptys) {
            return true
        }
        i, j := emptys[idx][0], emptys[idx][1]
        b := i / 3 * 3 + j / 3
        for num := byte('1'); num <= '9'; num++ {
            if rows[i][num] || cols[j][num] || blocks[b][num] {
                continue
            }
            rows[i][num], cols[j][num], blocks[b][num] = true, true, true
            board[i][j] = num
            if backtrack(idx + 1) {
                return true
            }
            rows[i][num], cols[j][num], blocks[b][num] = false, false, false
        }
        return false
    }
    backtrack(0)
}
// 高效写法
func solveSudoku(board [][]byte) {
    blocks, rows, cols := [9][9]int{},  [9][9]int{},  [9][9]int{}
    emptys := [][2]int{} // 需要填写的位置
    for i := 0; i < 9; i++ {
        for j := 0; j < 9; j++ {
            if board[i][j] == '.' {
                emptys = append(emptys, [2]int{i, j})
            } else {
                b := i / 3 * 3 + j / 3
                n := board[i][j] - '1'
                rows[i][n]++
                cols[j][n]++
                blocks[b][n]++
            }
        }
    }
    var backtrack func(int) bool
    backtrack = func(idx int) bool {
        if idx == len(emptys) {
            return true
        }
        i, j := emptys[idx][0], emptys[idx][1]
        b := i / 3 * 3 + j / 3
        for num := 0; num < 9; num++ {
            if rows[i][num] == 1 || cols[j][num] == 1 || blocks[b][num] == 1 {
                continue
            }
            rows[i][num], cols[j][num], blocks[b][num] = 1, 1, 1
            board[i][j] = byte(num + '1')
            if backtrack(idx + 1) {
                return true
            }
            rows[i][num], cols[j][num], blocks[b][num] = 0, 0, 0
        }
        return false
    }
    backtrack(0)
}

3. 双向BFS的实现、特性和题解

实战题目

func ladderLength(beginWord string, endWord string, wordList []string) int {

}
// 一条基因序列由一个带有8个字符的字符串表示,其中每个字符都属于 "A", "C", "G", "T"中的任意一个
func minMutation(start string, end string, bank []string) int {

}

课后作业

  • 总结双向 BFS 代码模版,请同学们提交在第 7 周学习总结中。

4. 启发式搜索的实现、特性和题解

参考链接

实战题目

// A* Search 启发式搜索
type Node struct {
    x, y, f int
}
func shortestPathBinaryMatrix(grid [][]int) (cnt int) {
    
}
// bfs
func shortestPathBinaryMatrix(grid [][]int) (cnt int) {
    m, n := len(grid), len(grid[0])
    if grid[0][0] == 1 || grid[m - 1][n - 1] == 1{
        return -1
    }
    if m == 1 && n == 1 {
        return 1
    }
    dx := []int{0, 1, 0, -1, -1, 1, -1, 1}
    dy := []int{1, 0, -1, 0, -1, 1 , 1, -1}
    visited := make([][]bool, m)
    for i := range visited {
        visited[i] = make([]bool, n)
    }
    queue := [][2]int{{0, 0}}
    visited[0][0] = true
    for len(queue) > 0 {
        cnt++
        for size := len(queue); size > 0; size-- {
            i, j := queue[0][0], queue[0][1]
            queue = queue[1:]
            for k := 0; k < 8; k++ {
                x, y := i + dx[k], j + dy[k]
                if x >= 0 && x < m && y >= 0 && y < n && !visited[x][y] && grid[x][y] != 1 {
                    if x == m - 1 && y == n - 1 {
                        return cnt + 1
                    }
                    queue = append(queue, [2]int{x, y})
                    visited[x][y] = true   
                }
            }
        }
    }
    return -1
}
func slidingPuzzle(board [][]int) int {

}
// 方法二:位预算优化
// 方法三:枚举优化
// 方法一:高效写法
func solveSudoku(board [][]byte) {
    blocks, rows, cols := [9][9]int{},  [9][9]int{},  [9][9]int{}
    emptys := [][2]int{} // 需要填写的位置
    for i := 0; i < 9; i++ {
        for j := 0; j < 9; j++ {
            if board[i][j] == '.' {
                emptys = append(emptys, [2]int{i, j})
            } else {
                b := i / 3 * 3 + j / 3
                n := board[i][j] - '1'
                rows[i][n]++
                cols[j][n]++
                blocks[b][n]++
            }
        }
    }
    var backtrack func(int) bool
    backtrack = func(idx int) bool {
        if idx == len(emptys) {
            return true
        }
        i, j := emptys[idx][0], emptys[idx][1]
        b := i / 3 * 3 + j / 3
        for num := 0; num < 9; num++ {
            if rows[i][num] == 1 || cols[j][num] == 1 || blocks[b][num] == 1 {
                continue
            }
            rows[i][num], cols[j][num], blocks[b][num] = 1, 1, 1
            board[i][j] = byte(num + '1')
            if backtrack(idx + 1) {
                return true
            }
            rows[i][num], cols[j][num], blocks[b][num] = 0, 0, 0
        }
        return false
    }
    backtrack(0)
}

第15课 | 红黑树和AVL树

AVL树和红黑树的实现和特性

参考链接

本周作业及下周预习

请同学们优先完成中等难度题目,每周的作业要求是提交 2 道作业题目,其他题目可根据自身情况选择练习。

本周作业

简单

中等

困难

下周预习

预习知识点:

预习题目: