算法总结(一)-回溯问题

102 阅读2分钟

1.什么是回溯问题

解决一个回溯问题,本质上就是解决一个决策树的遍历过程。这类问题要抓住三个要点:

  • 1、路径:已经做出的选择。

  • 2、选择列表:你当前可以做的选择。

  • 3、结束条件:到达决策树底层,⽆法再做选择的条件。

    一个典型的回溯问题就是全排列,n个不重复的数排序种类(n=3时决策树如下,边为选择,点为状态,从根节点到叶子节点的路径为一种排序方式,所以有多少个叶子节点就有多少种排序方式)

image.png

2.解决回溯问题的框架

var result [][]int 
func backtrack(路径, 选择列表) {
    if 满⾜结束条件 {
        result = append(result, 路径)
        return
    }
    for 选择 in 选择列表 {
        # 做选择
        将该选择从选择列表移除
        路径 = append(路径, 选择)
        backtrack(路径, 选择列表)
        # 撤销选择
        路径 = 路径[:len(路径)-1]
        将该选择再次加入选择列表
   }
}

其核⼼就是 for 循环⾥⾯的递归,在递归调⽤之前“做选择”,在递归调⽤ 之后“撤销选择”,在做选择和撤销选择的时候注意选择列表和路径的变化,还要注意结束条件中res放入路径的副本,避免后续对路径的修改影响已经加入到res中的路径元素。

// 主函数,输入一组不重复的数字,返回它们的全排列
var res [][]int
func permute(nums []int) [][]int {
    res = [][]int{}
    // 记录「路径」
    track := []int{}
    backtrack(nums, track)
    return res
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
func backtrack(nums []int, track []int) {
    // 触发结束条件
    if len(track) == len(nums) {
        cp := make([]int, len(track))
        copy(cp, track)
        res = append(res, cp)
        return
    }

    for i := 0; i < len(nums); i++ {
        if find(nums[i], track) {
            continue
        }
        track = append(track, nums[i])
        backtrack(nums, track)
        track = track[:len(track)-1]	
    }
}

func find(target int, nums []int) bool {
    if len(nums) == 0 {
        return false
    }

    for _, item := range nums {	
        if item == target {
                return true	
        }
    }
    return false
}

3.N皇后问题

最后看一个N皇后问题来加固回溯框架,这也是一个典型的回溯问题,规则为:给你一个 N×N 的棋盘,让你放置 N 个皇后,使得它们不能互相攻击。皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

  • 首先定义一个回溯方法func backtrack(board [][]byte, row int) 表示已经正确放置了row-1行棋盘,现在要遍历第row行棋盘。
  • 然后理解三要素:
    • 路径:board 中小于 row 的那些行都已经成功放置了皇后
    • 选择列表:第 row 行的所有列都是放置皇后的选择
    • 结束条件:row 超过 board 的最后一行 代码如下:
var res [][]string
func solveNQueens(n int) [][]string {
    res = make([][]string, 0)
    boardByte := make([][]byte, n)
    for i := 0; i < n; i++ {
        boardByte[i] = make([]byte, n)
        for j := 0; j < n; j++ {
            boardByte[i][j] = '.'
        }
    }
    backtrack(boardByte, 0)
    return res
}

func backtrack(board [][]byte, row int) {
    if row == len(board) {
        tmp := formatString(board)
        res = append(res, tmp)
        return
    }
    for i := 0; i < len(board); i++ {
        if !isValid(board, row, i) {
            continue
        }
        board[row][i] = 'Q'
        backtrack(board, row+1)
        board[row][i] = '.'
    }
}

func isValid(board [][]byte, row, col int) bool {
    for i := 0; i < row; i++ {
        if board[i][col] == 'Q' {
            return false
        }
    }
    for i, j := row-1, col+1; i >= 0 && j < len(board); i, j = i-1, j+1 {
        if board[i][j] == 'Q' {
            return false
        }
    }
    for i, j := row-1, col-1; i >= 0 && j >= 0; i, j = i-1, j-1 {
        if board[i][j] == 'Q' {
            return false
        }
    }
    return true
}

func formatString(board [][]byte) []string {
    ans := make([]string, len(board))
    for i := 0; i < len(board); i++ {
        ans[i] = string(board[i])
    }
    return ans
}