1.什么是回溯问题
解决一个回溯问题,本质上就是解决一个决策树的遍历过程。这类问题要抓住三个要点:
-
1、路径:已经做出的选择。
-
2、选择列表:你当前可以做的选择。
-
3、结束条件:到达决策树底层,⽆法再做选择的条件。
一个典型的回溯问题就是全排列,n个不重复的数排序种类(n=3时决策树如下,边为选择,点为状态,从根节点到叶子节点的路径为一种排序方式,所以有多少个叶子节点就有多少种排序方式)
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
}