回溯算法模板/框架 来源于<五分钟算法>
- 回溯算法就是个多叉树的遍历问题,关键就是在前序遍历和后续遍历的位置做一些操作
result = []
func BackTrack(路径,选择列表){
if 满足结束条件{
result = append(result,路径)
return
}
// 核心就是for 循环里面的递归
for 选择 in 选择列表{
做选择
BackTrack(路径,选择列表)
撤销选择
}
}
思考步骤:
- 1、路径:也就是已经做出的选择
- 2、选择列表: 也就是你当前可以做的选择
- 3、结束条件: 也就是到达决策树底层,无法再做选择的条件
决策树
- 解决一个回溯问题,实际上就是一个决策树的遍历问题
各种搜索问题其实都是树的遍历问题,而多叉树的遍历框架:
func Traverse(TreeNode root){
for _,child := range root{
//前序遍历操作,在进入某一个节点之前的那个时间点执行
Traverse(child)
//后续遍历操作,在离开某个节点之后的那个时间点执行
}
}
总结
-
必须说明的时,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 N! ,因为穷举整颗决策树是无法避免的,回溯算法就是纯暴力穷举,复杂度一般都很高。
-
写 backtrack 函数时,需要维护走过的 路径 和当前可以做的 选择列表,当触发 结束条件 时,将 路径记入结果集。
-
回溯对比动态规划: 动态规划的三个需要明确的点就是 状态 选择 和 base case,是不是就对应着走过的 路径,当前的 选择列表 和 结束条件 ??
-
某种程度上,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质, 可以用 dp table 或者备忘录优化, 将递归树大幅剪枝,就变成了动态规划。
全排列问题
- n个不重复的数,全排列共有 n! 个
var res []int
//主函数,输入一组不重复的数字,返回它们的全排列
func Permute(nums []int)[]int{
//记录路径
track := []int{}
backTrack(nums,track)
return res
}
func backTrack(nums []int,track []int){
//触发结束条件
if len(track) == len(nums){
res = append(res,track...)
return
}
for i:=0;i<len(nums);i++{
// 排除不合法的选择
for j:=0;j<len(track);j++{
if nums[i] == track[j]{continue}
}
// 做选择
track = append(track,nums[i])
// 进入下一层决策树
backTrack(nums,track)
// 取消选择
track = track[:len(track)-1]
}
}
N皇后问题
-
给你一个 N×N 的棋盘,让你放置 N 个皇后,使得它们不能互相攻击。
-
PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。
题目分析
- 问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行
- 每个节点可以做出的选择是,在该行的任意一列放置一个皇后
代码实现
- 直接套用框架:
var res [][]int
//主函数, 输入棋盘边长n,返回所有合法的放置
func SolveNQueens(n int)[][]int{
// '.' 表示空, 'Q' 表示皇后, 初始化空棋盘
board := make([][]int,n)
backTrack(board,0)
return res
}
func backTrack(board [][]int,row int){
//触发结束条件
if row == len(board){
res = append(res,board...)
return
}
n := len(board[row])
for col:=0;col<n;col++ {
// 排除不合法选择
if ok := isValid(board,row,col);!ok{
continue
}
// 做选择
board[row][col] = 1
// 进入下一轮抉择
backTrack(board,row+1)
// 撤销选择
board[row][col] = 0
}
}
// 是否可以在 board[row][col] 放置皇后
func isValid(board [][]int,row int,col int)bool{
n := len(board)
// 检查列是否有皇后相互冲突
for i := 0;i<n;i++{
if board[i][col] == 1{return false}
}
// 检查右上方是否有皇后相互冲突
for i,j:=row-1,col+1; i>=0&&j<n; i,j =i-1,j+1{
if board[i][j] == 1{return false}
}
// 检查左上方是否有皇后相互冲突
for i,j:=row-1,col-1; i>=0&&j>=0; i,j = i-1,j-1{
if board[i][j] == 1{return false}
}
return true
}