LeetCode46-全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
一、思路
(一)可移动数
这个方法需要记录每个数的移动方向(向左or向右),而且当最大数不能移动时,还要寻找下一个可移动的最大数,移动之后,更改移动方向,如此下去,直到所有数都不能移动。
原本想用一个布尔向量存放移动方向,但是它们之间的映射关系不一定成立
比如:nums=[3, 5, 7, 90]这样的全排列,必须使用哈希表才能对应起来,这么一来操作很麻烦,效率也很低
(二)字典序
设P是1~n的一个全排列:
p
=
p
1
p
2
.
.
.
.
.
.
p
j
−
1
p
j
p
j
+
1
.
.
.
.
.
.
p
k
−
1
p
k
p
k
+
1
.
.
.
.
.
.
p
n
p=p_1p_2......p_{j-1}p_jp_{j+1}......p_{k-1}p_kp_{k+1}......p_n
p=p1p2......pj−1pjpj+1......pk−1pkpk+1......pn
(1)从排列的右端开始,找出第一个比右边数字小的数字的序号
j
j
j(
j
j
j从左端开始计算),即
j
=
m
a
x
{
i
∣
p
i
<
p
i
+
1
}
j=max\{i|p_i<p_{i+1}\}
j=max{i∣pi<pi+1}
(2)在
p
j
p_j
pj的右边的数字中,找出所有比
p
j
p_j
pj大的数中最小的数字
p
k
p_k
pk,即
k
=
m
a
x
{
i
∣
p
i
>
p
j
}
k=max\{i|p_i>p_j\}
k=max{i∣pi>pj}(右边的数从右至左是递增的,因此
k
k
k是所有大于
p
j
p_j
pj的数字中序号最大者)
(3)对换
p
j
,
p
k
p_j,p_k
pj,pk
(4)再将
p
j
+
1
.
.
.
.
.
.
p
k
−
1
p
k
p
k
+
1
.
.
.
.
.
.
p
n
p_{j+1}......p_{k-1}p_kp_{k+1}......p_n
pj+1......pk−1pkpk+1......pn倒转得到排列
p
′
=
p
1
p
2
.
.
.
.
.
p
j
−
1
p
j
p
n
.
.
.
.
.
p
k
+
1
p
k
p
k
−
1
.
.
.
.
.
p
j
+
1
p'=p_1p_2.....p_{j-1}p_jp_n.....p_{k+1}p_kp_{k-1}.....p_{j+1}
p′=p1p2.....pj−1pjpn.....pk+1pkpk−1.....pj+1,这就是排列
p
p
p的下一个排列。
详细的可以参考LeetCode31-下一个排列:添加链接描述
C++代码
class Solution {
public:
vector<vector<int>> results;
vector<vector<int>> permute(vector<int>& nums) {
if (nums.empty())
return results;
int i, j, k, n = 1;
sort(nums.begin(), nums.end());
//results.push_back(nums);
for (i = 1; i <= nums.size(); i++)
n *= i;
for (int k = 0; k < n; k++) {
for (i = nums.size() - 1; i > 0; i--) {
// 从右往左,找到第一个小于右边的j
if (nums[i - 1] < nums[i]) {
for (j = nums.size() - 1; j >= i; j--) {
if (nums[j] > nums[i - 1]) {
nums[i - 1] = nums[j] + nums[i - 1];
nums[j] = nums[i - 1]-nums[j];
nums[i - 1] = nums[i - 1] - nums[j];
break;
}
}
break;
}
}
reverse(nums, i, nums.size() - 1);
results.push_back(nums);
}
return results;
}
void reverse(vector<int>& nums, int begin, int end) {
while (begin < end) {
nums[begin] = nums[begin] + nums[end];
nums[end] = nums[begin] - nums[end];
nums[begin] = nums[begin] - nums[end];
begin++;
end--;
}
return;
}
};
执行效率:
(三)、回溯算法
这个问题也可以使用回溯算法来解决。
算法流程:
设置一个向量,存放排列
(1)判断当前将要取出的数是否被标记
- 标记则表示:之前使用过这个数,跳至下一轮
- 未标记,则表示这个没有使用过,放入向量,结束这一轮
(2)反复执行(1)直到向量的元素个数=总的元素个数
C++代码:
class Solution {
public:
vector<vector<int>> results;
vector<bool> pos;
vector<vector<int>> permute(vector<int>& nums) {
if (nums.empty())
return results;
vector<int> res;
vector<bool> p(nums.size(), true);
pos = p;
recall(nums, res);
return results;
}
void recall(vector<int>& nums, vector<int>& res) {
if (res.size() == nums.size()) {
results.push_back(res);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (pos[i]) {
pos[i] = false;
res.push_back(nums[i]);
recall(nums, res);
pos[i] = true;
res.pop_back();
}
}
}
};
执行效率: