本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
46. 全排列
来源:力扣(LeetCode)
给定一个不含重复数字的数组 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 中的所有整数 互不相同
解法
- 递归: 遍历元素,剩余的列表元素按照同样的方法求得组合,再对组合进行遍历,将左边遍历的元素加入到每种组合的前面作为最终的一种组合,再将该组合放入到结果列表中;
- 回溯:按顺序枚举每一位可能出现的情况,已经选择的数字在 当前 要选择的数字中不能出现。按照这种策略搜索就能够做到 不重不漏。
- 每一个结点表示了求解全排列问题的不同的阶段,这些阶段通过变量的「不同的值」体现,这些变量的不同的值,称之为「状态」;
- 使用深度优先遍历有「回头」的过程,在「回头」以后, 状态变量需要设置成为和先前一样 ,因此在回到上一层结点的过程中,需要撤销上一次的选择,这个操作称之为「状态重置」;
- 深度优先遍历,借助系统栈空间,保存所需要的状态变量,在编码中只需要注意遍历到相应的结点的时候,状态变量的值是正确的,具体的做法是:往下走一层的时候,path 变量在尾部追加,而往回走的时候,需要撤销上一次的选择,也是在尾部操作,因此 path 变量是一个栈;
- 深度优先遍历通过「回溯」操作,实现了全局使用一份状态变量的效果。
- 首先这棵树除了根结点和叶子结点以外,每一个结点做的事情其实是一样的,即:在已经选择了一些数的前提下,在剩下的还没有选择的数中,依次选择一个数,这显然是一个 递归 结构;
- 递归的终止条件是: 一个排列中的数字已经选够了 ,因此我们需要一个变量来表示当前程序递归到第几层,我们把这个变量叫做 depth,或者命名为 index ,表示当前要确定的是某个全排列中下标为 index 的那个数是多少;
- 布尔数组 used,初始化的时候都为 false 表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 true ,这样在考虑下一个位置的时候,就能够以 O(1)的时间复杂度判断这个数是否被选择过,这是一种「以空间换时间」的思想。
代码实现
递归
python实现
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if n == 1:
return [nums]
res = []
for i in range(n):
pre_num = nums[i]
right_parts = self.permute(nums[:i]+nums[i+1:])
for part in right_parts:
res.append([pre_num]+part)
return res
c++实现
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
int n = nums.size();
if (n == 1)
return {nums};
vector<vector<int>> res;
for(int i=0; i<n; i++){
int left_num = nums[i];
vector<int> new_num = nums;
new_num.erase(new_num.begin()+i);
vector<vector<int>> right_parts = permute(new_num);
for(auto part: right_parts){
part.insert(part.begin(), left_num);
res.push_back(part);
}
}
return res;
}
};
复杂度分析
- 时间复杂度:
- 空间复杂度:
回溯
python实现
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
size = len(nums)
if size == 1:
return [nums]
used = [False for _ in range(size)]
res = []
def dfs(nums, size, depth, path, used):
if depth == size:
res.append(path[:]) # 这里要加:, 深拷贝,变量 path 所指向的列表 在深度优先遍历的过程中只有一份 ,深度优先遍历完成以后,回到了根结点,成为空列表
return res
for i in range(size):
if not used[i]:
used[i] = True
path.append(nums[i])
dfs(nums, size, depth+1, path, used)
used[i] = False
path.pop()
dfs(nums, size, 0, [], used)
return res
c++实现
class Solution {
vector<vector<int>> res;
public:
void dfs(vector<int>& nums, int size, int depth, vector<int> path, vector<bool> used) {
if (depth == size) {
res.push_back(path);
return;
}
for (int i=0; i<size; i++) {
if (!used[i]){
used[i] = true;
path.push_back(nums[i]);
dfs(nums, size, depth+1, path, used);
used[i] = false;
path.pop_back();
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
int size = nums.size();
if (size == 1)
return {nums};
vector<bool> used(size, false);
dfs(nums, size, 0, {}, used);
return res;
}
};
复杂度分析
- 时间复杂度:
- 空间复杂度: