46. 全排列

6 阅读3分钟

给定一个不含重复数字的数组 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. 第一步(选择) :谁站第一个?可以是张三、李四或王五。
  2. 第二步(记录) :如果张三站了第一个,那第二个位置只能从李四和王五里选。
  3. 第三步(回溯/撤销) :如果刚才选了张三排头,李四排二,王五排三。记录完这种排法后,李四得从第二个位置下来,王五也得下来,换一种排法(比如王五站第二个)。

这个“先尝试、再撤回、换个路子再试”的过程,在算法里就叫“回溯”。


💻 代码实现与生活化注释

这是你图片中代码的完整版本,我加入了一些“生活化”的注释来帮你理解每一行的逻辑:

JavaScript

/**
 * @param {number[]} nums - 这一队参与排队的人(比如 [1, 2, 3])
 * @return {number[][]} - 所有的排队方案
 */
var permute = function(nums) {
    let res = []; // 最终的记事本,用来记录所有成功的排队方案
    
    // 标记位:记录谁已经在队伍里了,防止一个人分身成两个
    let isUsed = new Array(nums.length).fill(false);

    /**
     * 回溯函数:就像是在排队现场指挥
     * @param {number[]} path - 当前已经在排队的“小分队”
     */
    let backtrack = (path) => {
        // 【停止条件】:如果当前小分队的人数和总人数一样,说明这一轮排好了!
        if (path.length === nums.length) {
            res.push([...path]); // 把这个方案完整地抄在记事本上
            return; // 这一轮结束,回到上一步换人
        }

        // 遍历每一个人,看看谁能站到当前的空位上
        for (let i = 0; i < nums.length; i++) {
            // 如果这个人已经在队伍里了,就不能重复选,跳过
            if (isUsed[i]) continue;

            // 【做选择】:让这个人进队
            path.push(nums[i]);
            isUsed[i] = true; // 在名单上给他打个勾,标记“已在队中”

            // 【递归】:继续给下一个位置找人
            backtrack(path);

            // 【回溯/撤销选择】:这是最关键的一步!
            // 等上面的递归结束了,说明这条路走到底了。
            // 我们要把最后进队的那个人请出来,腾出位置给下一种可能。
            path.pop(); 
            isUsed[i] = false; // 把名单上的勾擦掉,让他可以参与下一种排队组合
        }
    }

    backtrack([]); // 从一个空队伍开始排
    return res; // 返回最后记事本上所有的方案
};

🗝️ 核心逻辑拆解

为了让你理解得更透彻,我们可以把这个过程看作一棵决策树

  • 路径 (path) :就是你当前正在尝试的排列方式。
  • 选择列表 (nums) :当前位置你可以选哪些人。
  • 结束条件:当 path 的长度等于 nums 的长度,说明所有人都有位子了。
  • 回溯的魔力path.pop()isUsed[i] = false 就像是**“时光倒流”**。它让你在尝试完一种可能性后,能够干净利落地回到上一个状态,去尝试另一种可能性。

如果不做“撤销”的操作,你第一轮排完 [1, 2, 3] 之后,大家就都锁死在位置上了,没法尝试 [1, 3, 2] 这种新组合了。这就是为什么代码里一定要有那两行“还原”代码的原因。