(整理自labuladong的算法小抄,顺便推荐这本书)
输出一个数组的所有全排列结果(leetcode#46)
DFS
var permute = function (nums) {
if (nums.length === 1) return [nums]
// nums的全排列 = nums的第i个数字 + nums除i之外的全排列
const res = []
for (let i in nums) {
const newNums = nums.slice()
newNums.splice(i, 1)
for (let pRes of permute(newNums)) {
pRes.unshift(nums[i])
res.push(pRes)
}
}
return res
}
回溯
var permute = function(nums) {
let res = []
bt([], nums, res)
return res
};
var bt = function(track, options, res) {
if(track.length === options.length) {
res.push(track.slice())
}
for(let num of options){
if(!(track.findIndex(n=>n === num) > -1)){
track.push(num)
bt(track,options,res)
track.pop()
}
}
}
整个过程都在使用同一个track数组对象,通过不停的添加和删除元素来改变数组的值,在满足结束条件时将这个值的浅拷贝记录下来。 本质上是对决策树的遍历,在决策树的每一个节点都维护当前track的状态和可选择列表,当遍历到叶子节点时我们向上回溯决策树,直到遍历完整个决策树。
输出一个数组的所有子集(leetcode#78)
var subsets = function(nums) {
let res = []
bt([],nums,0,res)
return res
};
var bt = function(track, options, start, res) {
res.push(track.slice())
for(let i = start; i < options.length; i++){
track.push(options[i])
bt(track, options, i+1, res)
track.pop()
}
}
相同的套路,变化的是结束条件没有了,但是通过控制nums的起点来缩小选择的范围
组合 return all possible combinations of k numbers out of the range [1, n]
var combine = function(n, k) {
let res = []
if(n <= 0 || k <= 0) return res
bt([],n, 1, k, res)
return res
};
var bt = function(track,n,start,k,res) {
if(track.length === k){
res.push(track.slice())
}
for(let i = start; i < n + 1; i++){
track.push(i)
bt(track,n,i+1,k,res)
track.pop()
}
}
总结
本质都是对决策树枝的遍历和收集,但是考虑的结束条件、剪枝策略不同。