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:恢复现场,继续尝试别的数

十、结论

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

它的核心思想是:

  • 每一层确定一个位置放什么
  • 通过交换把候选数字放到当前位置
  • 递归处理剩余位置
  • 处理完再恢复原数组
相关推荐
wunianor2 小时前
[算法]2026年3月14日米哈游校招算法笔试题题解
算法
仟濹2 小时前
【算法打卡day25(2026-03-17 周二)今日算法:「回溯算法」】1-力扣17-电话号码的字母组合 2-力扣39-组合总和 3-力扣40-组合总和II
算法·leetcode·职场和发展
Storynone2 小时前
【Day26】LeetCode:452. 用最少数量的箭引爆气球,435. 无重叠区间,763. 划分字母区间
python·算法·leetcode
月明长歌2 小时前
【码道初阶-Hot100】LeetCode 3. 无重复字符的最长子串:从错误直觉到滑动窗口,彻底讲透为什么必须判断 `map.get(c) >= left`
java·算法·leetcode·哈希算法
junnhwan2 小时前
LeetCode Hot 100——贪心算法
java·算法·leetcode
魑魅魍魉都是鬼2 小时前
java 的排序算法
java·算法·排序算法
2401_853576502 小时前
并行算法在STL中的应用
开发语言·c++·算法
晓纪同学2 小时前
ROS2 -06-动作
java·数据库·python·算法·机器人·ros·ros2
无限进步_2 小时前
【C++】字符串中的字母反转算法详解
开发语言·c++·ide·git·算法·github·visual studio