回溯算法篇--全排列

159 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

1.题目一

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]

输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]

输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]

输出:[[1]]  

提示:

1 <= nums.length <= 6

-10 <= nums[i] <= 10

nums 中的所有整数 互不相同


思路

回溯法三部曲

  • 递归函数的返回值以及参数
  • 回溯函数终止条件
  • 单层搜索的过程 还是那句话画图能更清晰了解,此处我就不画图了。

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。 而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

递增子序列考虑的情况更多,这也是需要注意的点,use 是记录本层元素是否重复使用,新的一层use都会重新定义(清空),所以要知道use只负责本层!

那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

当前递归结束后,要将当前的选择撤销,回到选择前的状态,去考察另一个选择,即进入下一轮迭代,尝试另一种切分的可能。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次

这样才能在解的空间树中,把路走全了,搜出所有的合法部分解。


代码

var permute = function(nums) {
    const res = [], path = [];
    backtracking([]);
    return res;
    
    function backtracking(used) {
        if(path.length === nums.length) {
            res.push(path.slice())
            return
        }
        for(let i = 0; i < nums.length; i++) {
            if(used[i]) continue
            path.push(nums[i])
            used[i] = true
            backtracking(used)
            console.log(i)
            used[i] = false
            path.pop()
        }
    }
};