LeetCode-44 回溯解法

全排列:一篇看懂回溯解法

题目

给定一个不含重复数字的整数数组 nums,返回它的所有全排列。

示例

java 复制代码
输入:nums = [1,2,3]

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

一、题目

这道题的核心是:

把数组中的每个数字,都轮流放到当前位置上,直到所有位置都放满。

比如 nums = [1,2,3]

  • 第一位可以放 1
  • 第一位也可以放 2
  • 第一位还可以放 3

每确定一位,就继续递归处理下一位。

这就是典型的 回溯 问题。


二、最容易想到的思路

可以这样理解:

  • 先确定第 0 个位置放谁
  • 再确定第 1 个位置放谁
  • 再确定第 2 个位置放谁
  • 当所有位置都确定后,就得到一个排列

为了高效,我们通常使用 交换 的方式,而不是每次都新建数组。


三、回溯解法

代码

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        backtrack(nums, 0, res);
        return res;
    }

    private void backtrack(int[] nums, int first, List<List<Integer>> res) {
        if (first == nums.length) {
            List<Integer> list = new ArrayList<>();
            for (int num : nums) {
                list.add(num);
            }
            res.add(list);
            return;
        }

        for (int i = first; i < nums.length; i++) {
            swap(nums, first, i);              // 选择一个数放到当前位置
            backtrack(nums, first + 1, res);   // 递归处理下一个位置
            swap(nums, first, i);              // 恢复现场
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

四、为什么这样做可行

假设数组是:

java 复制代码
[1,2,3]

第一步:确定第 0 位

  • 1 放到第 0 位,数组还是 [1,2,3]
  • 2 放到第 0 位,数组变成 [2,1,3]
  • 3 放到第 0 位,数组变成 [3,2,1]

第二步:递归确定后面的位

例如已经确定第 0 位是 1,那后面就去处理 [2,3] 的排列。

最后会得到:

  • [1,2,3]
  • [1,3,2]

再继续处理第 0 位是 2、第 0 位是 3 的情况。


五、为什么交换后还要交换回来

这是回溯最关键的一步。

例如当前数组是:

java 复制代码
[1,2,3]

当我们尝试把 2 放到第 0 位时,会执行:

java 复制代码
swap(nums, 0, 1)

数组变成:

java 复制代码
[2,1,3]

递归处理完以后,如果不换回来,数组就一直是 [2,1,3],会影响后面继续尝试别的情况。

所以必须再交换一次:

java 复制代码
swap(nums, 0, 1)

把数组恢复成原来的样子:

java 复制代码
[1,2,3]

这样下一轮尝试才不会出错。

一句话概括:

交换过去是做选择,交换回来是撤销选择。


六、执行过程示例

nums = [1,2,3] 为例:

第一层:确定第 0 位

  • 1,得到 [1,2,3]
  • 2,得到 [2,1,3]
  • 3,得到 [3,2,1]

第二层:确定第 1 位

例如当前是 [1,2,3]

  • 2,得到 [1,2,3]
  • 3,得到 [1,3,2]

第三层:确定第 2 位

只剩最后一个数,直接加入结果。

最终得到全部 6 种排列。


七、复杂度分析

时间复杂度

text 复制代码
O(n × n!)

原因:

  • 一共有 n! 种排列
  • 每次加入答案时,需要把当前数组复制到结果中,花费 O(n)

空间复杂度

text 复制代码
O(n)

主要是递归调用栈的深度。

不计最终答案所占空间。


八、这道题的本质

这道题的本质不是"交换",而是:

枚举每个位置可以放哪些数。

交换只是一个实现手段,它的优点是:

  • 不需要额外创建很多新数组
  • 代码更简洁
  • 性能更好

九、总结

这道题是经典的回溯题。

可以记住下面这个模板:

java 复制代码
做选择
递归
撤销选择

对应到本题就是:

java 复制代码
swap(nums, first, i);
backtrack(nums, first + 1, res);
swap(nums, first, i);

其中:

  • swap:把一个数放到当前的位置
  • backtrack:递归处理下一个位置
  • swap:恢复现场,继续尝试别的数

十、结论

全排列问题最经典的解法就是 回溯 + 交换

它的核心思想是:

  • 每一层确定一个位置放什么
  • 通过交换把候选数字放到当前位置
  • 递归处理剩余位置
  • 处理完再恢复原数组
相关推荐
BothSavage10 小时前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn10 小时前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法
烬羽12 小时前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试
先吃饱再说1 天前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰1 天前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术1 天前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize2 天前
初识DFS 与 BFS:递归、队列与图遍历
算法