问题描述

给定一个不含重复数字的数组 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 中的所有整数 互不相同

回溯

核心思路

首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。

可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

但排列问题需要一个used数组,标记已经选择的元素。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used) {
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
  • 时间复杂度:O(n×n!)O(n \times n!),其中 nn 为序列的长度。

    算法的复杂度首先受 backtrack\textit{backtrack} 的调用次数制约,backtrack\textit{backtrack} 的调用次数为 k=1nP(n,k)\sum_{k = 1}^{n}{P(n, k)} 次,其中 P(n,k)=n!(nk)!=n(n1)(nk+1)P(n, k) = \frac{n!}{(n - k)!} = n (n - 1) \ldots (n - k + 1),该式被称作 n 的 k - 排列,或者部分排列。而 k=1nP(n,k)=n!+n!1!+n!2!+n!3!++n!(n1)!<2n!+n!2+n!22+n!2n2<3n!\sum_{k = 1}^{n}{P(n, k)} = n! + \frac{n!}{1!} + \frac{n!}{2!} + \frac{n!}{3!} + \ldots + \frac{n!}{(n-1)!} < 2n! + \frac{n!}{2} + \frac{n!}{2^2} + \frac{n!}{2^{n-2}} < 3n!

    这说明 backtrack\textit{backtrack} 的调用次数是 O(n!)O(n!) 的。

    而对于 backtrack\textit{backtrack} 调用的每个叶结点(共 n!n! 个),我们需要将当前答案使用 O(n)O(n) 的时间复制到答案数组中,相乘得时间复杂度为 O(n×n!)O(n \times n!)

    因此时间复杂度为 O(n×n!)O(n \times n!)

  • 空间复杂度:O(n)O(n),其中 nn 为序列的长度。除答案数组以外,递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,这里可知递归调用深度为 O(n)O(n)