一、问题描述:
借助这道题总结一下全排列问题吧
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; } };