21. 全排列【LC46】回溯算法

163 阅读1分钟

题目:

给定一个不含重复数字的数组 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,xxxxx”这种直接跳过,不成立的。

下面以[1,2,3]这个数组为例,来看具体的回溯思路。

每一位都有3种选择:1、2、3。
每一次都做选择,展开出一棵空间树。

利用约束条件「不能重复选」,做剪枝,剪去不会产生正确解的选项(分支)。
利用 hashMap,记录选过的数,下次遇到相同的数,跳过。

dfs 函数:基于当前的 path,继续选数,直到构建出合法的 path,加入解集。

维护状态:公用一个path,并且维护一个used标识path中当前元素的状态。

递归的入口:dfs 执行传入空 path,什么都还没选。

函数体内,用 for loop 枚举出当前所有的选项,并用 if 语句跳过剪枝项。

每一轮迭代,作出一个选择,基于它,继续选(递归调用)。 递归的出口:当构建的 path 数组长度等于 nums 长度,就选够了,加入解集。

image.png

解:

const permute = (nums) => {
    const res = [];
    const used = {}; //公用,伴随stack做状态标识

    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;
};

———— 前端、Javascript实现、算法、刷题、leetcode