LC46 Permutation
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], [3,2,1] ]
回溯的思路,具体实现有两种方法。
方法一:在nums里面挨个挑数字放到目标排列的第一个位置上,然后把选中的数从nums里面删除,循环这个过程。nextPermute()中nums就是剩下的可放在第一个位置的备选数组,每次从里面挑一个数然后缩短这个备选数组传到下一次挑用。
void nextPermute(vector<int> nums, vector<vector<int>>& res, vector<int>& cur){
if(nums.size()==0){
res.push_back(cur);
return;
}
vector<int> nextNums=nums, nextCur=cur;
for(int i=0;i<nums.size();i++){
nextCur.push_back(nums[i]);
nextNums.erase(nextNums.begin()+i);
nextPermute(nextNums, res, nextCur);
nextNums=nums;
nextCur=cur;
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> cur;
nextPermute(nums, res, cur);
return res;
}
这个方法比较直观,我第一次就想到了这么做。缺点:1)频繁的数组erase操作比较耗时;2)nums的传值拷贝比较浪费空间
方法二:和方法一类似,还是每次从数组后面挑选各个数放到最前面。使用swap()实现这个过程。
void myPermute(vector<int>& nums, vector<vector<int>>& res, int pos) {
if(pos==nums.size()){
res.push_back(nums);
return;
}
for(int i=pos;i<nums.size();i++){
swap(nums[i], nums[pos]);
myPermute(nums, res, pos+1);
swap(nums[i], nums[pos]);
//不停的把pos后面的各个数字替换到pos的位置。观察从pos开始的子串,这个过程保证了首个位置(pos)可以是后面的任意一个只值
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
myPermute(nums, res, 0);
return res;
}
比较好的地方是可以直接通过引用传递传nums。注意myPermute()之后也要再把之前swap的两个数swap回来。理由如下,考虑[1,2,3],在没有第二个swap的情况下依次得出的排列是:[1,2,3]->[1,3,2]->[3,1,2]->[3,2,1]->下一个会是什么呢?因为没有swap回来并且使用引用传递,下一步pos=0,i=2,将第一个位置的数和最后一个位置的数进行换位,也就是得到->[1,2,3]->[1,3,2],也就出现了重复。可以看到,使用引用传值这个trick还是很tricky的tricky的,在一开始写代码的时候就像弄对还是比较难的,一次次的传值就不需要考虑递归调用的层次对nums的干扰、对递归的其他分支的干扰。
LC47 Permutation 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], [2,1,1] ]
问题和LC46基本一样,多了一个条件就是nums里面可能存在重复值。有了LC46的swap方法,很直接的就能想到在每次将后面的值swap到前面的过程中多一个判断跳过已经swap到首位的值,这样避免了重复。为了顺序的判断,先把nums排序。代码:
void myPermute(vector<int> nums, vector<vector<int>>& res, int startPos){
if(startPos==nums.size()-1){
res.push_back(nums);
return;
}
for(int i=startPos;i<nums.size();i++){
if(i!=startPos&&nums[i]==nums[startPos])
continue;
swap(nums[i],nums[startPos]);
myPermute(nums,res,startPos+1);
//myPermute函数不能用引用传递传nums,这一点和Permutation那道题不同
}
}
vector<vector<int>> permuteUnique(vector<int>& nums){
sort(nums.begin(),nums.end());
vector<vector<int>> res;
myPermute(nums, res, 0);
return res;
}
要注意的点比较多,1)nums必须用pass-by-value;2)myPermute只有不能再swap。通过一些test case能发现不这么做会出错,例如[1,1,2,2]。比较科学的解释暂时还没有想好,有待继续做深入的研究。
回溯的方法在涉及到pass-by-reference时要谨慎考虑不同分支间可能存在的相互影响,但是回溯递归的程序一般又难以清晰的理解其运行顺序。可能我题目练的还是太少了,或者没有掌握别的分析回溯的黑科技。加油吧。
当然这个题目可以直接用set来排出重复,那样的话就简单多了,直接套LC46的代码再加一步去重就行了。