转自:https://leetcode-cn.com/probl...
思路
每一位都有3种选择:1、2、3。
每一次都做选择,展开出一棵空间树,如下图。
利用约束条件「不能重复选」,做剪枝,剪去不会产生正确解的选项(分支)。
利用 hashMap,记录选过的数,下次遇到相同的数,跳过。
这样就不会进入「不会得出解的分支」,不做无效的搜索。
怎么写递归函数
我们要在这个包含解的空间树上,用 DFS 的方式搜索出所有的解。
dfs 函数:基于当前的 path,继续选数,直到构建出合法的 path,加入解集。
递归的入口:dfs 执行传入空 path,什么都还没选。
函数体内,用 for loop 枚举出当前所有的选项,并用 if 语句跳过剪枝项。
每一轮迭代,作出一个选择,基于它,继续选(递归调用)。
递归的出口:当构建的 path 数组长度等于 nums 长度,就选够了,加入解集。
为什么要回溯
我们不是找到一个排列就完事,要找出所有满足条件的排列。
当一个递归调用结束,结束的是当前的递归分支,还要去别的分支继续搜。
所以,要撤销当前的选择,回到选择前的状态,再选下一个选项,即进入下一个分支。
注意,往 map 存入的当前选择也要撤销,表示撤销这个选择。
退回来,把路走全,才能在一棵空间树中,回溯出所有的解。
代码
jsgo
const permute = (nums) => {
const res = [];
const used = {};
function dfs(path) {
if (path.length == nums.length) { // 个数选够了
res.push(path.slice()); // 拷贝一份path,加入解集res
return; // 结束当前递归分支
}
for (const num of nums) { // for枚举出每个可选的选项
// if (path.includes(num)) continue; // 别这么写!查找是O(n),增加时间复杂度
if (used[num]) continue; // 使用过的,跳过
path.push(num); // 选择当前的数,加入path
used[num] = true; // 记录一下 使用了
dfs(path); // 基于选了当前的数,递归
path.pop(); // 上一句的递归结束,回溯,将最后选的数pop出来
used[num] = false; // 撤销这个记录
}
}
dfs([]); // 递归的入口,空path传进去
return res;
};
解答评论区的困惑
为什么加入解集时,要将数组内容拷贝到一个新的数组里,再加入解集?
因为该 path 变量存的是地址引用,结束当前递归时,将它加入 res 后,该算法还要进入别的递归分支继续搜索,还要继续将这个 path 传给别的递归调用,它所指向的内存空间还要继续被操作,所以 res 中的 path 的内容会被改变,这就不对。
所以要弄一份当前的拷贝,放入 res,这样后续对 path 的操作,就不会影响已经放入 res 的内容。
作者:xiao_ben_zhu
链接:https://leetcode-cn.com/probl...
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。