回溯算法合集。要注意“回退步骤”,比如往数组里添加一个数,然后递归,则递归完要将该数再删除,才能实现原路径返回(回溯)
回溯是一种算法思想,可以用递归实现
组合(中等)
- 思路:递归。维护一个子数组 path,首先存入一个数,作为开始数,后续存入的每一个数都做放入和不放入 path 的处理,找到所有的结果,每当 path 的长度等于 k 的时候就将 path 存入目标数组,然后再更改开始数,如此反复
var combine = function(n, k) {
if (n < k) return []
const res = [], path = []
let dfs = (n, k, begin, path, res) => {
if (path.length === k) {
// push 参数如果是对象,则传入的是引用,必须转换(拷贝)一下
res.push([...path]) // res.push(JSON.parse(JSON.stringify(path)))
return
}
for (let i = begin; i <= n; i++) {
path.push(i)
dfs(n, k, i + 1, path, res)
path.pop() // 回溯
}
}
dfs(n, k, 1, path, res)
return res
};
子集 II(中等)
- 思路:递归。思路大致同上,通过回溯,找到每一种情况。难点是有重复数字时,如何给子集去重,详情见代码注释
var subsetsWithDup = function(nums) {
if (!nums.length) return []
nums.sort((a, b) => a - b)
let res = [], path = []
let dfs = (choose, cur, nums) => {
if (cur === nums.length) {
res.push([...path])
return
}
// 如果不存入当前数
dfs(false, cur + 1, nums)
// 如果当前数等于上一位数,且上一位数未被选择,则返回,防止出现重复子集
if (!choose && cur > 0 && nums[cur - 1] === nums[cur]) {
return
}
path.push(nums[cur])
// 如果存入当前数
dfs(true, cur + 1, nums)
path.pop() // 回溯
}
dfs(false, 0, nums)
return res
};
组合总和(中等)
- 思路:递归。思路了同上,代码有一处细节值得推敲
var combinationSum = function(candidates, target) {
let res = []
let dfs = (target, sub, idx) => {
if (idx === candidates.length) return
if (target === 0) {
res.push(sub)
return
}
dfs(target, sub, idx + 1)
if (target - candidates[idx] >= 0) {
// 为何此处未做回溯的“回退”步骤?答案见代码底部
dfs(target - candidates[idx], [...sub, candidates[idx]], idx)
}
}
dfs(target, [], 0)
return res
};
- 因为此处是用扩展运算符对 sub 进行拷贝了,后续递归并没有直接改变 sub 的内容,所以当回溯到这一步时当前 sub 并未改变,不需要“回退步骤”
如果觉得对你有帮助的话,点个赞呗~
反正发文又不赚钱,交个朋友呗~
如需转载,请注明出处foolBirdd