一、问题描述:
借助这道题总结一下全排列问题吧
https://leetcode.com/problems/permutations/
Given a collection of distinct numbers, return all possible permutations.
For example,
[1,2,3]
have the following permutations:
[1,2,3]
, [1,3,2]
, [2,1,3]
, [2,3,1]
, [3,1,2]
, and [3,2,1]
.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
https://leetcode.com/problems/permutations-ii/
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
For example,
[1,1,2]
have the following unique permutations:
[1,1,2]
, [1,2,1]
, and [2,1,1]
.
比较直观的想法就是为每个元素维护一个数组,判断到某一时刻时各个元素是否已被使用过,选择没有使用过的元素向下一时刻递归。这种方法需要额外的空间开销,并且多次比较时间上会产生浪费。
观察排列[1,2,3] 其余排列与它的关系可视为交换对应位置元素得到的,于是可以想到用交换的方式生成全排列。
那么如何尝试所有交换并且不发生重复呢?当然是回溯(递归),但是如何高效的递归?如下
void myPermute(vector<vector<int>> &ans, vector<int>& nums, int startPos) {
if (startPos == nums.size()-1) {
ans.push_back(nums);
}
else {
for (int i = startPos; i < nums.size(); ++i) {
swap(nums, startPos, i);
myPermute(ans, nums, startPos+1);
swap(nums, startPos, i);
}
}
}
在myPermute()中,通过startPos限制交换的范围,每次要固定其之前的元素不变,尝试在之后的所有元素中进行交换,并且递归调用startPos+1,返回后需要将状态恢复。
swap()函数也比较简单:
void swap(vector<int>& nums, int pos1, int pos2) {
int tmp= nums[pos2];
<span style="white-space:pre"> </span>nums[pos2] = nums[pos1];
<span style="white-space:pre"> </span>nums[pos1] = tmp;
}
也可以装一波提高效率的swap:
void swap(vector<int>& nums, int pos1, int pos2) {
nums[pos1] ^= nums[pos2];
nums[pos2] ^= nums[pos1];
nums[pos1] ^= nums[pos2];
}
然而,坑点来了!这也是我写这篇文章的驱动力之一 = =
WA!为什么呢?
显然是因为装逼的原因...
注意:
1. 异或操作的两个元素如果相同的话,会产生0!
所以为了将初始排列包括进去,对myPermute()修改如下:
void myPermute(vector<vector<int>> &ans, vector<int>& nums, int startPos) {
if (startPos == nums.size()) {
ans.push_back(nums);
}
else {
myPermute(ans, nums, startPos+1);//filter the element-self
for (int i = startPos+1; i < nums.size(); ++i) {
swap(nums, startPos, i);
myPermute(ans, nums, startPos+1);
swap(nums, startPos, i);
}
}
}
显然这不是一种通用的方法,因为题目I中说了没有重复元素,所以如果有重复元素,过滤也不管用啊!
2. 异或0得到元素本身:
虽然这个题目中貌似没有测到0,不过还是比较坑的...
全排列解决了,去重全排列的问题就比较简单了,只需要加上一个判断重复函数:
void myPermute(vector<vector<int>> &ans, vector<int>& nums, int startPos) {
if (startPos == nums.size()-1) {
ans.push_back(nums);
}
else {
for (int i = startPos; i < nums.size(); ++i) {
if (!findDuplicate(nums, startPos, i)) {
swap(nums, startPos, i);
myPermute(ans, nums, startPos+1);
swap(nums, startPos, i);
}
}
}
}
bool findDuplicate(vector<int> &nums, int pos1, int pos2) {
for (; pos1 < pos2; ++pos1) {
if (nums[pos1] == nums[pos2]) {
return true;
}
}
return false;
}
findDuplicate()保证从[pos1, pos2)之间没有与pos2相同的元素时才进行交换,如果有的话要保证nums[pos1]与其最接近的nums[pos2]进行交换,否则会产生重复。
三、源码及总结:
问题比较简单,但是是非常经典的问题,里面也能不小心从装逼失败中发现一些细节问题。下面只贴II的代码了,I只用把if判断注释掉就行了。
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
myPermute(ans, nums, 0);
return ans;
}
private:
void myPermute(vector<vector<int>> &ans, vector<int>& nums, int startPos) {
if (startPos == nums.size()-1) {
ans.push_back(nums);
}
else {
for (int i = startPos; i < nums.size(); ++i) {
if (!findDuplicate(nums, startPos, i)) {
swap(nums, startPos, i);
myPermute(ans, nums, startPos+1);
swap(nums, startPos, i);
}
}
}
}
bool findDuplicate(vector<int> &nums, int pos1, int pos2) {
for (; pos1 < pos2; ++pos1) {
if (nums[pos1] == nums[pos2]) {
return true;
}
}
return false;
}
void swap(vector<int>& nums, int pos1, int pos2) {
int tmp = nums[pos2];
nums[pos2] = nums[pos1];
nums[pos1] = tmp;
}
};