回溯算法框架

回溯算法模板/框架 来源于<五分钟算法>

  • 回溯算法就是个多叉树的遍历问题,关键就是在前序遍历和后续遍历的位置做一些操作
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
}

数独问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值