第4周 DFS、BFS、贪心、二分查找

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

题目数量:20

第9课 | 深度优先搜索和广度优先搜索

1. 深度优先搜索、广度优先搜索的实现和特性

参考链接

2. 实战题目解析:二叉树的层次遍历等问题

实战题目

/**
 * 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 nil
    }
    queue := []*TreeNode{root}
    for len(queue) > 0 {
        level := []int{}
        for size := len(queue); size > 0; size-- {
            node := queue[0]
            queue = queue[1:]
            level = append(level, node.Val)
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        res = append(res, level)
    }
    return res
}
// dfs
func levelOrder(root *TreeNode) (res [][]int) {
    if root == nil {
        return nil
    }
    var dfs func(level int, root *TreeNode)
    dfs = func(level int, root *TreeNode) {
        if root == nil {
            return
        }
        if len(res) < level {
            res = append(res, []int{})
        }
        res[level - 1] = append(res[level - 1], root.Val)
        dfs(level + 1, root.Left)
        dfs(level + 1, root.Right)
    }
    dfs(1, root)
    return res
}
// bfs
func minMutation(start string, end string, bank []string) (ans int) {
    mp := map[string]bool{}
    for _, b := range bank {
        mp[b] = true
    }
    if !mp[end] || len(start) != len(end){
        return -1
    }
    ws := []byte{'A', 'C', 'G', 'T'}
    used := map[string]bool{}
    queue := []string{start}
    used[start] = true
    n := len(start)
    for len(queue) > 0 {
        for size := len(queue); size > 0; size-- {
            w := queue[0]
            if w == end {
                return ans
            }
            queue = queue[1:]
            b := []byte(w)
            for i := 0; i < n; i++ {
                o := b[i]
                for _, c := range ws {
                    b[i] = c
                    if used[string(b)] {
                        continue
                    }
                    if mp[string(b)] {
                        queue = append(queue, string(b))
                        used[string(b)] = true
                    }
                }
                b[i] = o
            }
        }
        ans++
    }
    return -1
}
// dfs,还有其他写法
func generateParenthesis(n int) (res []string) {
    var generate func(int, int, string)
    generate = func(left, right int, path string) {
        if left == n && right == n {
            res = append(res, path)
            return
        }
        if left < n {
            generate(left + 1, right, path + "(")
        }
        if right < left {
            generate(left, right + 1, path + ")")
        }
    }
    generate(0, 0, "")
    return res
}
// bfs
// 其他写参考官方
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
// bfs
func largestValues(root *TreeNode) (res []int) {
    if root == nil {
        return res
    }
    queue := []*TreeNode{root}
    for len(queue) > 0 {
        max := math.MinInt32
        for size := len(queue); size > 0; size-- {
            node := queue[0]
            queue = queue[1:]
            if max < node.Val {
                max = node.Val
            }
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        res = append(res, max)
    }
    return res
}
// dfs

课后作业

// 单bfs
func ladderLength(beginWord string, endWord string, wordList []string) int {
    mp := map[string]struct{}{}
    for _, word := range wordList {
        mp[word] = struct{}{}
    }
    mp[beginWord] = struct{}{}
    if _, ok := mp[endWord]; !ok {
        return 0
    }
    cnt := 0
    queue := []string{beginWord}
    visited := map[string]struct{}{}
    visited[beginWord] = struct{}{}
    wordLen := len(beginWord)
    for len(queue) > 0 {
        cnt++
        for size := len(queue); size > 0; size-- {
            w := queue[0]
            queue = queue[1:]
            s := []byte(w)
            // for i, b := range s {
            for i := 0; i < wordLen; i++ {
                b := s[i]
                for j := byte('a'); j <= 'z'; j++ {
                    if j == b {
                        continue
                    }
                    s[i] = j
                    nextWord := string(s)
                    if _, ok := mp[nextWord]; !ok {
                        continue
                    }
                    if nextWord == endWord {
                        return cnt + 1
                    }
                    if _, ok := visited[nextWord]; !ok {
                        queue = append(queue, nextWord)
                        visited[nextWord] = struct{}{}
                    }
                }
                s[i] = b
            }
        }
    }
    return 0
}
// 双向 bfs
func ladderLength(beginWord string, endWord string, wordList []string) int {
    mp := map[string]struct{}{}
    for _, word := range wordList {
        mp[word] = struct{}{}
    }
    mp[beginWord] = struct{}{}
    if _, ok := mp[endWord]; !ok {
        return 0
    }
    cnt := 0
    beginMap := map[string]struct{}{}
    beginMap[beginWord] = struct{}{}
    endMap := map[string]struct{}{}
    endMap[endWord] = struct{}{}
    visited := map[string]struct{}{}
    wordLen := len(beginWord)
    for len(beginMap) > 0 && len(endMap) > 0 {
        cnt++
        if len(beginMap) > len(endMap) {
            beginMap, endMap = endMap, beginMap
        }
        tmpMap := map[string]struct{}{}
        for w, _ := range beginMap {
            s := []byte(w)
            for i := 0; i < wordLen; i++ {
                b := s[i]
                for j := byte('a'); j <= 'z'; j++ {
                    if j == b {
                        continue
                    }
                    s[i] = j
                    nextWord := string(s)
                    if _, ok := mp[nextWord]; !ok {
                        continue
                    }
                    if _, ok := endMap[nextWord]; ok {
                        return cnt + 1
                    }
                    if _, ok := visited[nextWord]; !ok {
                        tmpMap[nextWord] = struct{}{}
                        visited[nextWord] = struct{}{}
                    }
                }
                s[i] = b
            }
        }
        beginMap = tmpMap
    }
    return 0
}
// bfs
func findLadders(beginWord string, endWord string, wordList []string) (res [][]string) {
    mp := map[string]struct{}{}
    for _, word := range wordList {
        mp[word] = struct{}{}
    }
    if _, ok := mp[endWord]; !ok {
        return res
    }
    from := map[string][]string{}
    from[beginWord] = []string{}
    steps := map[string]int{}
    queue := []string{beginWord}
    step := 0
    var found bool
    for len(queue) > 0 {
        step++
        for size := len(queue); size > 0; size-- {
            curWord := queue[0]
            queue = queue[1:]
            s := []byte(curWord)
            for i, b := range s {
                for j := byte('a'); j <= 'z'; j++ {
                    s[i] = j
                    nextWord := string(s)
                    if steps[nextWord] == step {
                        from[nextWord] = append(from[nextWord], curWord)
                    }
                    if _, ok := mp[nextWord]; !ok {
                        continue
                    }
                    delete(mp, nextWord)
                    queue = append(queue, nextWord)
                    if _, ok := from[nextWord]; !ok {
                        from[nextWord] = []string{}
                    }
                    from[nextWord] = append(from[nextWord], curWord)
                    steps[nextWord] = step
                    if nextWord == endWord {
                        found = true
                    }
                }
                s[i] = b
            }
        }
        if found {
            step++
            break
        }
    }
    path := make([]string, step)
    path[step - 1] = endWord
    var dfs func(string, int)
    dfs = func(preWord string, idx int) {
        if preWord == beginWord {
            res = append(res, append([]string(nil), path...))
            return
        }
        for _, pre := range from[preWord] {
            path[idx - 1] = pre
            dfs(pre, idx - 1)
            path[idx - 1] = ""
        }
    }
    dfs(endWord, step - 1)
    return res
}
// 双向 bfs
func findLadders(beginWord string, endWord string, wordList []string) (res [][]string) {
    mp := map[string]struct{}{}
    // 因为需要快速判断扩展出的单词是否在 wordList 里,因此需要将 wordList 存入哈希表,这里命名为「字典」
    for _, word := range wordList {
        mp[word] = struct{}{}
    }
    // 特殊用例判断
    if _, ok := mp[endWord]; !ok {
        return res
    }
    from := map[string][]string{}
    // 第 1 步:广度优先遍历建图
    // 记录扩展出的单词是在第几次扩展的时候得到的,key:单词,value:在广度优先遍历的第几层
    steps1 := map[string]int{beginWord: 0}
    steps2 := map[string]int{endWord: 0}
    q1 := []string{beginWord}
    q2 := []string{endWord}
    step :=[2]int{}
    found := false
    update := func(q []string, steps1, steps2  map[string]int, flag int) []string {
        step[flag]++
        for size := len(q); size > 0; size-- {
            currWord := q[0]
            q = q[1:]
            s := []byte(currWord)
            for i, b := range s {
                // 将每一位替换成 26 个小写英文字母
                for j := byte('a'); j <= 'z'; j++ {
                    s[i] = j
                    nextWord := string(s)
                    if _, ok := mp[nextWord]; !ok {
                        continue
                    }
                    stepTmp, ok := steps1[nextWord]
                    if ok && step[flag] > stepTmp {
                        continue
                    } 
                    if !ok || (ok && stepTmp == step[flag]) {
                        if flag == 0 {
                            from[nextWord] = append(from[nextWord], currWord)
                        } else {
                            from[currWord] = append(from[currWord], nextWord)
                        }
                    }
                    // 这一层扩展出的单词进入队列
                    q = append(q, nextWord)
                    // 记录 nextWord 的 step
                    steps1[nextWord] = step[flag]
                    // 当前层找到了
                    if _, ok := steps2[nextWord]; ok {
                        found = true
                    }
                }
                //改回原单词
                s[i] = b
            }
        }
        if found {
            step[flag]++
        }
        return q
    }
    for len(q1) > 0 && len(q2) > 0 && !found {
        if len(q1) < len(q2) {
            q1 = update(q1, steps1, steps2, 0)
        } else {
            q2 = update(q2, steps2, steps1, 1)
        }
    }
    // 第 2 步:深度优先遍历找到所有解,从 endWord 恢复到 beginWord ,所以每次尝试操作 path 列表的头部
    if found {
        l := step[0] + step[1]
        path := make([]string, l)
        var dfs func(string, int)
        dfs = func(nextWord string, idx int) {
            if nextWord == beginWord {
                res = append(res, append([]string(nil), path...))
                return 
            }
            for _, word := range from[nextWord] {
                path[idx - 1] = word
                dfs(word, idx - 1)
                path[idx - 1] = ""
            }
        }
        path[l - 1] = endWord
        dfs(endWord, l - 1)
    }
    return res
}
// 命名规范版本
func findLadders(beginWord string, endWord string, wordList []string) (res [][]string) {
	mp := map[string]struct{}{}
	for _, word := range wordList {
		mp[word] = struct{}{}
	}
	if _, ok := mp[endWord]; !ok {
		return res
	}
	from := map[string][]string{}
	beginStep, endStep := map[string]int{beginWord: 0}, map[string]int{endWord: 0}
	beginQueue, endQueue := []string{beginWord}, []string{endWord}
	steps := [2]int{}
	var found bool
	update := func(q []string, step1, step2 map[string]int, flag int) []string {
		steps[flag]++
		for size := len(q); size > 0; size-- {
			curWord := q[0]
			q = q[1:]
			s := []byte(curWord)
			for i, b := range s {
				for j := byte('a'); j <= 'z'; j++ {
					s[i] = j
					nextWord := string(s)
					if _, ok := mp[nextWord]; !ok || j == b {
						continue
					}
                    oldStep, ok := step1[nextWord]
                    if ok && oldStep < steps[flag] {
                        continue
                    }
					if !ok || oldStep == steps[flag] {
						if flag == 0 {
							from[nextWord] = append(from[nextWord], curWord)
						} else {
							from[curWord] = append(from[curWord], nextWord)
						}
					}
					q = append(q, nextWord)
					step1[nextWord] = steps[flag]
					if _, ok := step2[nextWord]; ok {
						found = true
					}
				}
				s[i] = b
			}
		}
		if found {
			steps[flag]++
		}
		return q
	}
	for len(beginQueue) > 0 && len(endQueue) > 0 && !found {
		if len(beginQueue) <= len(endQueue) {
			beginQueue = update(beginQueue, beginStep, endStep, 0)
		} else {
			endQueue = update(endQueue, endStep, beginStep, 1)
		}
	}
	if found {
		step := steps[0] + steps[1]
		path := make([]string, step)
		path[step-1] = endWord
		var dfs func(string, int)
		dfs = func(s string, i int) {
			if s == beginWord {
				res = append(res, append([]string(nil), path...))
				return
			}
			for _, word := range from[s] {
				path[i-1] = word
				dfs(word, i-1)
				//path[i-1] = ""
			}
		}
		dfs(endWord, step-1)
	}
	return res
}
// dfs
// bfs
var fx = [][2]int{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}
func numIslands(grid [][]byte) (ans int) {
    n, m := len(grid), len(grid[0])
    for i := 0; i < n; i++ {
        for j := 0; j < m; j++ {
            if grid[i][j] == '1' {
                ans++
                grid[i][j] = '0'
                queue := [][2]int{{i, j}}
                for len(queue) > 0 {
                    cur := queue[0]
                    queue = queue[1:]
                    for _, f := range fx {
                        ni, nj := cur[0] + f[0], cur[1] + f[1]
                        if 0 <= ni && ni < n && 0 <= nj && nj < m && grid[ni][nj] == '1' {
                            grid[ni][nj] = '0'
                            queue = append(queue, [2]int{ni, nj})
                        }
                    }
                }
            }
        }
    }
    return ans
}
// dfs

// bfs
func updateBoard(board [][]byte, click []int) [][]byte {
    n, m := len(board), len(board[0])
    queue := [][]int{click}
    for len(queue) > 0 {
        click = queue[0]
        queue = queue[1:]
        x, y := click[0], click[1]
        if board[x][y] == 'M' || board[x][y] == 'X' {
            board[x][y] = 'X'
        } else {
            //计算周围的地雷
            count := 0
            for i := -1; i <= 1; i++ {
                for j := -1; j <= 1; j++ {
                    if i == 0 && j == 0 {
                        continue
                    }
                    nx, ny := x + i, y + j
                    if 0 <= nx && nx < n && 0 <= ny && ny < m && (board[nx][ny] == 'M' || board[nx][ny] == 'X'){
                        count++
                    }
                }
            }

            //有地雷,标注地雷的个数
            if count > 0 {
                board[x][y] = byte(count + '0')
            } else {
                //没有地雷,标注为B
                board[x][y] = 'B'
                for i := -1; i <= 1; i++ {
                    for j := -1; j <= 1; j++ {
                        if i == 0 && j == 0 {
                            continue
                        }
                        nx, ny := x + i, y + j
                        if 0 <= nx && nx < n && 0 <= ny && ny < m && board[nx][ny] == 'E' {
                            queue = append(queue, []int{nx, ny})
                            board[nx][ny] = 'B'
                        }
                    }
                }
            }
        }
    }
    return board
}

第10课 | 贪心算法

贪心的实现、特性及实战题目解析

参考链接

func coinChange(coins []int, amount int) int {
    dp := make([]int, amount + 1)
    for i := 1; i <= amount; i++ {
        dp[i] = amount + 1 //初始化dp数组
        //循环各个面值,找到dp[i]最优解
        for _, coin := range coins {
            if coin <= i && dp[i] > dp[i - coin] + 1 {
                //递推公式
                dp[i] = dp[i - coin] + 1
            }
        }
    }
    if dp[amount] > amount {
        return -1
    }
    return dp[amount]
}

课后作业

// 写法一
func lemonadeChange(bills []int) bool {
    five, ten := 0, 0
    for _, bill := range bills {
        if bill == 5 {
            five++
        } else if bill == 10 { // 10元是5元找零
            five, ten = five - 1, ten + 1
        } else if ten > 0 { // 有10元
            five, ten =  five - 1, ten - 1
        } else { // 没有10元
            five -= 3
        }
        if five < 0 { // 判断5元个数是否小于0
            return false
        }
    }
    return true
}
// 写法二
func lemonadeChange(bills []int) bool {
    five, ten := 0, 0
    for _, bill := range bills {
        if bill == 5 {
            five++
        } else if bill == 10 {
            if five < 0 {
                return false
            }
            five--
            ten++
        } else { // 20
            if five > 0 && ten > 0 {
                five--
                ten--
            } else if five >= 3 {
                five -= 3
            } else {
                return false
            }
        }
    }
    return true
}
// 贪心
func maxProfit(prices []int) (ans int) {
    for i := 1; i < len(prices); i++ {
        if prices[i] > prices[i-1] {
            ans += prices[i] - prices[i-1]
        }
    }
    return ans
}
// dp
func maxProfit(prices []int) int {
    dp0, dp1 := 0, -prices[0]
    for i := 0; i < len(prices); i++ {
        dp0, dp1 = max(dp0, dp1 + prices[i]), max(dp0 - prices[i], dp1)
    }
    return dp0
}
func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}
func findContentChildren(g []int, s []int) int {
    sort.Ints(g)
    sort.Ints(s)
    child := 0
    for j := 0; child < len(g) && j < len(s); j++ {
        if g[child] <= s[j] {
            child++
        }
    }
    return child
}
// 效率最高写法
func robotSim(commands []int, obstacles [][]int) int {
    obsMap := map[int]bool{}
    for _, obstacle := range obstacles {
        key := calculateHashKey(obstacle)
        obsMap[key] = true
    }

     //方向数组
    dx := []int{0, 1, 0, -1}
    dy := []int{1, 0, -1, 0}

    ans := 0
    x, y := 0, 0
    direction := 0 //0-N 1-E 2-S 3-W
    for _, cmd := range commands {
        if cmd == -2 { // 左转
            direction = (direction + 3) % 4
        } else if cmd == -1 { // 右转
            direction = (direction + 1) % 4
        } else {
            for i := 0; i < cmd; i++ {
                nx := x + dx[direction]
                ny := y + dy[direction]

                if obsMap[calculateHashKey([]int{nx, ny})] {
                    break
                }
                x, y = nx, ny
            }
            if tVal := x * x + y * y; tVal > ans {
                ans = tVal
            }
        }
    }
    return ans
}

func calculateHashKey(obstacle []int) int {
    return (30000 + obstacle[0]) * 60001 + (30000 + obstacle[1])
}
// 反向
func canJump1(nums []int) bool {
    revCanJump := len(nums) - 1
    for i := revCanJump; i >= 0; i-- {
        if i + nums[i] >= revCanJump {
            revCanJump = i
        }
    }
    return revCanJump == 0
}

// 正向
func canJump(nums []int) bool {
    can := nums[0]
    for i := 1; i < len(nums); i++ {
        if can < i {
            return false
        }
        can = max(can, i + nums[i])
    }
    return true
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}
func jump(nums []int) (ans int) {
    n := len(nums)
    if n < 2 {
        return ans
    }
    ans = 1
    curMaxJump, preMaxJump := nums[0], nums[0]
    for i := 1; i < n; i++ {
        if i > preMaxJump {
            preMaxJump = curMaxJump
            ans++
        }
        if i + nums[i] > curMaxJump {
            curMaxJump = i + nums[i]
        }
    }
    return ans
}

第11课 | 二分查找

二分查找的实现、特性及实战题目解析

参考链接

实战题目

// 二分
// 牛顿迭代
// 二分
// 牛顿迭代

课后作业

// 标准二分
func search(nums []int, target int) int {
    l, r := 0, len(nums) - 1
    for l <= r {
        mid := l + (r - l) >> 1
        if nums[mid] == target {
            return mid
        } else if nums[l] <= nums[mid] { // nums[l] 可改为 nums[0]
            if nums[l] <= target && target < nums[mid] {
                r = mid - 1
            } else {
                l = mid + 1
            }
        } else {
            if nums[mid] < target && target <= nums[r] {
                l = mid + 1
            } else {
                r = mid - 1
            }
        }
    }
    return -1
}
// 位运算二分
// 二分查找
func searchMatrix(matrix [][]int, target int) bool {
    m, n := len(matrix), len(matrix[0])
    l, r := 0, m * n - 1
    for l <= r {
        mid := l + (r - l) >> 1
        num := matrix[mid/n][mid%n]
        if num < target {
            l = mid + 1
        } else if num > target {
            r = mid - 1
        } else {
            return true
        }
    }
    return false
}
// 官方使用系统库,一次、二次二分查找
// 写法一
func findMin(nums []int) int {
    low, high := 0, len(nums) - 1
    for low < high {
        pivot := low + (high - low) >> 1
        if nums[pivot] > nums[high] {
            low = pivot + 1
        } else {
            high = pivot
        }
    }
    return nums[low]
}
// 写法二: 复杂写法
func findMin(nums []int) int {
    l, r := 0, len(nums) - 1
    if nums[l] < nums[r] || l == r {
        return nums[l]
    }
    for l < r {
        m := l + (r - l) >> 1
        if nums[m] > nums[m + 1] {
            return nums[m + 1]
        }
        if nums[m - 1] > nums[m] {
            return nums[m]
        }
        if nums[l] < nums[m] {
            l = m + 1
        } else {
            r = m
        }
    }
    return nums[l]
}
  • 使用二分查找,寻找一个半有序数组 [4, 5, 6, 7, 0, 1, 2] 中间无序的地方 说明:同学们可以将自己的思路、代码写在第 4 周的学习总结中

本周作业及第6周预习

下周为期中考试周,无视频课程,会安排覃超老师进行答疑直播(具体时间以微信班级群通知为准)。请同学们自行复习~

本周作业

简单

中等

困难

第 6 周预习

预习知识点

预习题目