全排列
力扣原题链接
问题描述
给定一个不含重复数字的数组 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]]
解题思路
- 初始化一个列表
res
用于存储结果。 - 调用回溯函数
backtrack
,传入参数nums
数组、当前路径path
和标记数组used
。 - 在回溯函数中,如果当前路径的长度等于
nums
数组的长度,则将当前路径加入结果列表中。 - 遍历数组
nums
,如果当前数字已经被使用过,则跳过;否则将当前数字加入路径,并递归调用回溯函数。 - 回溯函数结束后,返回结果列表
res
。
算法步骤
- 初始化一个列表
res
用于存储结果。 - 调用回溯函数
backtrack
,传入参数nums
数组、当前路径path
和标记数组used
。 - 在回溯函数中,如果当前路径的长度等于
nums
数组的长度,则将当前路径加入结果列表中。 - 遍历数组
nums
,依次选择未使用过的数字加入路径中。 - 在选择数字之前,需要进行判断:
- 如果当前数字已经在路径中,则跳过。
- 将当前数字加入路径中,并标记为已使用。
- 递归调用回溯函数,继续寻找全排列。
- 回溯,撤销选择,将当前数字移出路径,并标记为未使用。
- 返回结果列表
res
。
复杂度分析
- 时间复杂度:回溯算法的时间复杂度取决于最终的结果数量,而结果数量取决于数组
nums
的长度。假设数组nums
的长度为n
,则时间复杂度为O(n!)
。 - 空间复杂度:回溯算法的空间复杂度取决于递归调用栈的深度和结果列表的空间占用。在递归过程中,栈的最大深度为
n
,结果列表的空间占用为O(n!)
,因此空间复杂度为O(n!)
。
Java 代码
写法一(used数组)
使用used【i】,判断复杂度为O(1)
class Solution {
List<List<Integer>> res = new ArrayList<>();
// 主函数,用于找到数组中的全排列
public List<List<Integer>> permute(int[] nums) {
backtrack(nums, new ArrayList<>(), new boolean[nums.length]); // 回溯函数的入口
return res; // 返回结果列表
}
// 回溯函数,用于寻找数组中的全排列
private void backtrack(int[] nums, List<Integer> path, boolean[] used) {
// 当路径的长度等于数组的长度时,将当前路径加入结果列表
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
// 遍历数组,依次选择未使用过的数字加入路径中
for (int i = 0; i < nums.length; i++) {
// 如果当前数字已经在路径中,则跳过
if (used[i]) {
continue;
}
// 将当前数字加入路径中,并标记为已使用
used[i] = true;
path.add(nums[i]);
// 递归调用回溯函数,继续寻找全排列
backtrack(nums, path, used);
// 回溯,撤销选择,将当前数字移出路径,并标记为未使用
path.remove(path.size() - 1);
used[i] = false;
}
}
}
写法二
使用ArrayList的contains判断,复杂度为O(n)
class Solution {
List<List<Integer>> res = new ArrayList<>();
// 主函数,用于找到数组中的全排列
public List<List<Integer>> permute(int[] nums) {
backtrack(nums, new ArrayList<>()); // 回溯函数的入口
return res; // 返回结果列表
}
// 回溯函数,用于寻找数组中的全排列
private void backtrack(int[] nums, List<Integer> path) {
// 当路径的长度等于数组的长度时,将当前路径加入结果列表
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
// 遍历数组,依次选择未使用过的数字加入路径中
for (int i = 0; i < nums.length; i++) {
// 如果当前数字已经在路径中,则跳过
if (path.contains(nums[i])) {
continue;
}
// 将当前数字加入路径中
path.add(nums[i]);
// 递归调用回溯函数,继续寻找全排列
backtrack(nums, path);
// 回溯,撤销选择,将当前数字移出路径
path.remove(path.size() - 1);
}
}
}