题目地址:
https://leetcode.com/problems/permutations/
给定一个长 n n n数组 A A A,求其全排列。题目保证 A A A中无重复数字。
法1:用经典的回溯算法,对应着多叉树的深搜。首先想象一下我们要深搜的树,可以画成下面的样子:
先设一个list,存储遍历这棵树的时候,当前走到的节点。每当遍历到叶子节点,也就是当前的list已经有了
3
3
3个数的时候,就要将这个list加入最后的结果集,然后回溯回去。所以v.size() == A.size()
就是递归的出口。
一开始我们站在树根,也就对应着什么也没选。接下来开始做决策。在树的第一层,我们有三个决策,对应着第一个数字选
1
1
1或
2
2
2或
3
3
3,一旦做完第一个决策,我们走到第二层的时候,继续做决策,我们发现,只要新加入的数字不在当前的list里,就可以将其加入。所以决策条件就是,新待加入的数字不在当前的list里。决策完了以后就递归遍历下一层子树。之后再撤销选择。代码如下:
class Solution {
public:
vector<vector<int>> permute(vector<int>& A) {
vector<vector<int>> res;
vector<int> v;
int vis = 0;
dfs(v, A, vis, res);
return res;
}
void dfs(vector<int>& v, vector<int>& A, int& vis, vector<vector<int>>& res) {
if (v.size() == A.size()) {
res.push_back(v);
return;
}
for (int i = 0; i < A.size(); i++) {
if (vis >> i & 1) continue;
vis |= 1 << i;
v.push_back(A[i]);
dfs(v, A, vis, res);
v.pop_back();
vis -= 1 << i;
}
}
};
空间复杂度为
O
(
n
∗
n
!
)
O(n*n!)
O(n∗n!),因为
n
n
n元排列有
n
!
n!
n!个,每个排列需要存储
n
n
n个数字。时间复杂度需要用递推方程计算,主要是要算helper方法的复杂度。设nums的长度为
n
n
n,cur的长度为
k
k
k时,helper花费的时间是
T
(
n
,
k
)
T(n,k)
T(n,k),则:
T
(
n
,
k
)
=
(
n
−
k
)
T
(
n
,
k
+
1
)
T
(
n
,
n
)
=
n
T(n,k)=(n-k)T(n,k+1)\\T(n,n)=n
T(n,k)=(n−k)T(n,k+1)T(n,n)=n有:
T
(
n
,
0
)
=
n
T
(
n
,
1
)
=
.
.
.
=
n
!
T
(
n
,
n
)
=
n
∗
n
!
T(n,0)=nT(n,1)=...=n!T(n,n)=n*n!
T(n,0)=nT(n,1)=...=n!T(n,n)=n∗n!所以时间复杂度是
O
(
n
!
n
)
O(n!n)
O(n!n)。
算法正确性证明:
首先证明任意一个排列都会被加入res。我们只需要根据某个具体的排列,来关注那个选择路径就可以了。比如对于排列 123... n 123...n 123...n,那么可以考虑第 0 0 0层helper的for循环在i=0时将 1 1 1加入了cur,接着开始递归下一层helper,在第 1 1 1层helper的for循环中在 i = 2 i=2 i=2时将 2 2 2加入了cur,直到第 n − 1 n-1 n−1层helper的for循环在 i = n − 1 i=n-1 i=n−1时将 n n n加入了cur,而此时,cur的长度等于 n n n,被加入了res。所以这一条证明完成。
其次证明任意一个排列只会被加入res一次。这一点是显然的,因为每一层for循环在遍历某个位置 i i i的时候只会pass一次。
法2: n n n元全排列也可以这么生成:从 12... n 12...n 12...n这个排列出发,我们先挑选谁成为最后一个数字,然后将 n n n与其进行交换(可以与自己交换),然后类似地递归挑选倒数第二个数字,并交换。每次挑选倒数第 k k k个数字的时候,只从 1 , . . . , n − k + 1 1,...,n-k+1 1,...,n−k+1中挑选,以避免重复。当挑选第 1 1 1个数字的时候,情况只有一种可能了,这就是递归出口。代码如下:
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
dfs(0, nums, res);
return res;
}
void dfs(int u, vector<int>& v, vector<vector<int>>& res) {
if (u == v.size()) {
res.push_back(v);
return;
}
for (int i = u; i < v.size(); i++) {
swap(v[i], v[u]);
dfs(u + 1, v, res);
swap(v[i], v[u]);
}
}
};
空间复杂度 O ( n ∗ n ! ) O(n*n!) O(n∗n!),有 n ! n! n!个子集,并且每个都要copy一份加入最终结果。设list长度为 n n n,pos等于 k − 1 k-1 k−1,时间复杂度有递推方程 T ( k ) = n + k T ( k − 1 ) T ( 0 ) = n T(k)=n+kT(k-1)\\T(0)=n T(k)=n+kT(k−1)T(0)=n所以有: T ( k ) − k T ( k − 1 ) = n k T ( k − 1 ) − k ( k − 1 ) T ( k − 2 ) = n k . . . k ! T ( 1 ) − k ! T ( 0 ) = n k ! T(k)-kT(k-1)=n\\kT(k-1)-k(k-1)T(k-2)=nk\\...\\k!T(1)-k!T(0)=nk! T(k)−kT(k−1)=nkT(k−1)−k(k−1)T(k−2)=nk...k!T(1)−k!T(0)=nk!累加起来得: T ( n ) = n ! n + n ! n ( ∑ i = 0 n 1 i ! ) = O ( n ∗ n ! ) T(n)=n!n+n!n(\sum_{i=0}^{n}\frac{1}{i!})=O(n*n!) T(n)=n!n+n!n(i=0∑ni!1)=O(n∗n!)