1.回溯算法理论基础
1.1 什么是回溯法
- 回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
- 回溯是递归的副产品,只要有递归就会有回溯。
- 回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案。
- 如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。
1.2 回溯法解决的问题
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
1.3 如何理解回溯法
回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
1.4 回溯法模板
- 回溯函数模板返回值以及参数
- 函数起名字为backtracking
- 回溯算法中函数返回值一般为void。
- 回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数
- void backtracking(参数)
- 回溯函数终止条件
- 一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。
if (终止条件) { 存放结果; return;}
- 回溯搜索的遍历过程
- 回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度
//回溯算法模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
2.组合问题
- 组合问题不关注答案中元素的排列顺序
- 所以我们只需要让元素依次递增即可
- 剪枝
- 当数组后面的元素个数小于还需要填充的数字个数时,就可以进行剪枝操作
var (
path []int
res [][]int
)
func combine(n int, k int) [][]int {
path, res = make([]int, 0, k), make([][]int, 0)
dfs(n,k,1)
return res
}
func dfs(n,k,start int) {
if len(path)==k{//满足了k个数的要求
tmp := make([]int,k)
copy(tmp,path)
res = append(res,tmp)
return
}
for i:= start;i<=n;i++{
if n - i + 1 < k - len(path) { // 剪枝,剩下的数全部填上也达不到k个数
break
}
path = append(path, i)
dfs(n,k,i+1)
path = path[:len(path)-1]
}
}
3. 组合总和3
var (
path []int
res [][]int
)
func combinationSum3(k int, n int) [][]int {
res = make([][]int, 0)
path = make([]int, 0)
trackingBack(0, n, k, 1)
return res
}
// temp当前path中的数字和
func trackingBack(tempSum, target, k, start int) {
if tempSum == target && k == len(path) {
temp := make([]int, len(path))
copy(temp, path)
res = append(res, temp)
}
// 剪枝1
if tempSum != target && k == len(path) {
return
}
for i := start; i <= 9; i++ {
path = append(path, i)
trackingBack(tempSum+i, target, k, i+1)
path = path[:len(path)-1]
}
}
4. 电话号码的字母组合
var (
path string
res []string
)
func letterCombinations(digits string) []string {
path = ""
res = make([]string,0)
if len(digits)==0{
return res
}
numToChars := [][]string{
{},
{},
{"a", "b", "c"},
{"d", "e", "f"},
{"g", "h", "i"},
{"j", "k", "l"},
{"m", "n", "o"},
{"p", "q", "r", "s"},
{"t", "u", "v"},
{"w", "x", "y", "z"},
}
trackingBack(digits, 0, numToChars)
return res
}
func trackingBack(digits string, index int, numToChars [][]string) {
if len(path) == len(digits) {
temp := path[:]
res = append(res, temp)
return
}
ceng := digits[index] - '0'
for i := 0; i < len(numToChars[ceng]); i++ {
path = path + numToChars[ceng][i]
trackingBack(digits, index+1, numToChars)
path = path[:len(path)-1]
}
}