回溯三部曲
-
递归函数参数和返回值
一般情况下,回溯算法返回值为空
backtracking(参数) -
确定递归的终止条件
回溯算法其实也是树形结构,那么遍历树形结构时一定要有终止条件,一般而言,遍历到叶子节点了就是找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。
if(终止条件){ 存放结果 return } -
确定单层搜索逻辑
回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成了树的深度。
回溯函数的遍历过程如下:
js for(选择:本层集合中元素(树中节点孩子的数量就是集合的大小)){ 处理节点 backstracking(路径,选择列表) 回溯,撤销处理结果 }
for循环就是遍历集合区间,一个节点有多少个孩子,for循环就执行多少次。
77. 组合
要求:给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案
思路
var combine = function(n, k) {
let path = [], res = []
function backstracking(n, startIndex){
if(path.length ==k){
res.push([...path])
return
}
for(let i=startIndex; i<=n; i++){
path.push(i)
backstracking(n, i+1)
path.pop()
}
}
backstracking(n, 1)
return res
};
回溯其实就是暴力算法,只是用回溯的方式取代位置遍历层数,但是回溯算法也可以进行剪枝优化。 可以剪枝的地方就在递归中每一层的for循环所选择的起始位置,如果for循环选择的起始位置之后的元素个数已经不足我们需要的元素个数了,那么就没有必要继续搜索了。
优化过程:
-
已经选择的元素个数:path.length
-
还需要选择的元素个数:k-path.length
-
在集合n中至多要从该起始位置:n-(k-path.length)+1
那么优化之后的for循环就是:
for(let i=startIndex; i <= n-(k-path.length)+1; i++ ){}