【Leetcode hot 100】46.全排列
问题链接
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
中的所有整数 互不相同
问题解答
核心思路
全排列的本质是穷举所有元素的不同顺序组合,由于需要“尝试-回溯-再尝试”的逻辑,因此适合用「回溯算法」解决。核心思想是:
- 选择元素:从数组中选一个未使用过的元素,加入当前排列路径。
- 递归探索:基于当前选择,继续选下一个未使用的元素,直到路径长度等于数组长度(即生成一个完整排列)。
- 回溯撤销:递归返回后,撤销上一步的选择(移除路径最后一个元素、标记元素为未使用),继续尝试其他可能性。
Java 代码
import java.util.ArrayList;
import java.util.List;class Solution {// 结果集:存储所有完整排列private List<List<Integer>> result = new ArrayList<>();// 路径:存储当前正在构建的排列private List<Integer> path = new ArrayList<>();public List<List<Integer>> permute(int[] nums) {// used数组:标记nums中哪些元素已被选入当前路径(避免重复选择)boolean[] used = new boolean[nums.length];// 启动回溯backtrack(nums, used);return result;}/*** 回溯函数* @param nums 原始数组(固定不变)* @param used 标记元素使用状态的数组*/private void backtrack(int[] nums, boolean[] used) {// 终止条件:当前路径长度 == 数组长度 → 生成一个完整排列if (path.size() == nums.length) {// 注意:需new ArrayList<>(path),避免后续修改path影响结果(path是引用类型)result.add(new ArrayList<>(path));return;}// 遍历数组:尝试选择每个未使用的元素for (int i = 0; i < nums.length; i++) {// 跳过已使用的元素if (used[i]) {continue;}// 1. 选择当前元素:标记为已使用,加入路径used[i] = true;path.add(nums[i]);// 2. 递归:基于当前选择,继续构建下一层路径backtrack(nums, used);// 3. 回溯:撤销上一步选择(恢复状态,供下一次循环尝试其他元素)path.remove(path.size() - 1); // 移除路径最后一个元素used[i] = false; // 标记为未使用}}
}
代码关键部分解释
-
数据结构作用:
result
:最终返回的所有排列集合,每个元素是一个完整的排列(List<Integer>
)。path
:临时存储当前正在构建的排列,比如从[1]
到[1,2]
再到[1,2,3]
。used
:布尔数组,长度与nums
一致,used[i] = true
表示nums[i]
已被选入当前path
,避免重复选择。
-
回溯函数流程(以
nums = [1,2,3]
为例):- 第一次调用
backtrack
:path
为空,遍历i=0
(nums[0]=1
未使用),标记used[0]=true
,path=[1]
,递归进入下一层。 - 第二层
backtrack
:path=[1]
,遍历i=1
(nums[1]=2
未使用),标记used[1]=true
,path=[1,2]
,递归进入下一层。 - 第三层
backtrack
:path=[1,2]
,遍历i=2
(nums[2]=3
未使用),标记used[2]=true
,path=[1,2,3]
。此时path.size() == 3
,将[1,2,3]
加入result
,返回上一层。 - 回溯操作:第三层返回后,执行
path.remove(2)
(path
变回[1,2]
),used[2]=false
;第二层继续遍历i=2
(nums[2]=3
未使用),重复上述逻辑生成[1,3,2]
,再回溯到第一层,继续尝试i=1
(nums[1]=2
),最终生成所有6种排列。
- 第一次调用
复杂度分析
- 时间复杂度:
O(n * n!)
总共有n!
个排列(n
为数组长度),每个排列需要O(n)
时间构建(将path
加入result
时需复制n
个元素)。 - 空间复杂度:
O(n)
递归栈深度最大为n
(构建完整排列时),path
和used
数组的大小均为n
,与n!
无关。
边界测试用例验证
- 示例1:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
(代码可生成所有6种排列)。 - 示例2:nums = [0,1]
输出:[[0,1],[1,0]]
(正常生成2种排列)。 - 示例3:nums = [1]
输出:[[1]]
(终止条件直接触发,加入唯一排列)。