题目:
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
思路:
题目要求很简练,相比之前的全排列问题,这个题目的难点在于nums包含重复数字序列。我们如何避免重复数字造成的重复解呢!
首先我们可以明确这道题采用dfs深度优先搜索的策略,我们构造一个搜索树,如何避免同一个元素被重复使用呢?这一点可以看全排列I的j解答:
下面的代码中使用了:
vector<int>::iterator it = find(track.begin(), track.end(), Num[i]);
if(it!=track.end())
continue;
注意到这个小技巧,我们知道哈希表可以用.find或者.count函数查找某个元素是否出现过,那么vector数组我们怎么查找呢。我们可以用上面的语句来查找如果找到就continue!因为全排列1问题中元素都不相同,所以一旦当前vector中出现该元素,就不搜索这个分支的解了。
但是我们现在的问题包含重复的元素,就不能简单用这个方法了,我们可以用vis数组来记录nums中每个元素是否被访问,如果被访问就给他1,如果没访问就给他0,如果访问过了,每一层关于他的分支就不走了。
完成了上面这一步,我们可以实现原数组中每个元素都只出现1次,然而我们仍然没有解决解可能重合的问题。其实想清楚什么情况下会重合然后对症下药就行了。我们首先明确我们树的深度就是nums的个数。那么每一层我们都会在路径解中增加一个元素或者说排列解中增加一个元素,每一层代表对应位置的赋值。那么为了实现不重复,针对重复的元素,在每一层都只出现某一个重复的元素个体,而不是你可以在这层来,我也可以在这层来。就是说在树的每一层,重复数字只会被填入一次,即每一层遇到重复数字填入的时候选中某一个重复数字填入就行了,因为他们本质是相同的,不就是填入一个数字,总数字减少一个~
怎么实现? 请看下面的代码:
for(int i=0; i<nums.size();i++){
if(vis[i] ||(i>0 && nums[i]==nums[i-1] && !vis[i-1]))
continue;
....
.....
}
理解:
说实话这个思想还是挺难理解的,首先我们需要将原数组从小大到排列,让数字相同的靠近在一起。注意到dfs中每一层的for都是独立的,需要将上次的操作撤销嘛。假设遇到21113 中间是1靠近一起的。首先如果vis[i]==1那就退出,什么情况vis[i]==1呢,我们知道每一层操作之前数字是没进去的,就是没走过,如果遇到这个数字走过,那说明是之前层这个位置的数字被利用过了,这边的遍历就可以舍去,这是保证每个数字走一次,这个我们可以理解~
那后面的一个条件又是什么意思呢? 就是说当我们遇到数字相同的情况,比如23334,我们的目标是每一层一个数字只出现1次。第一层第一个3能进入树,我们需后面两个3不要进去树,就是第一层的分布是 2 3 4 即可。怎么做到呢? 一旦出现num[i]=num[i-1],我们就break? 这样是不对的,这样会导致3的分支从后面后面就再也进不去新的3了。要再增加一个条件:
如果前一个元素进了我们排列元素内部,我们之前进排列元素内部肯定不是当前层,当前层还没进呢,那也就是说现在重复的元素还没开始占据当前坑位,我是可以进去的。
但是如果前面的还没进去 什么意思?就是说我现在是当层 前面一个肯定有一个组合在操作了,我就不凑热闹了,这样能保证 每次我们都让重复元素最左边的元素开始进行操作,操作完了同一层深度优先中其他的3就别想了,路上没人进去,但是你前面有个相同的,他肯定操作进去了。
这里i就是按顺序嘛,每一层都按顺序来看看nums中元素的资格,没事没事,最后上代码:
class Solution {
vector<int> vis;
public:
void backtrack(vector<int>& nums, vector<vector<int>>& ans, int idx, vector<int>& perm) {
if(idx==nums.size()){
ans.emplace_back(perm);
return;
}
for(int i=0; i<nums.size();i++){
if(vis[i] ||(i>0 && nums[i]==nums[i-1] && !vis[i-1]))
continue;
perm.emplace_back(nums[i]);
vis[i]=1;
backtrack(nums,ans,idx+1,perm);
perm.pop_back();
vis[i]=0;
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>>ans;
vector<int>perm;
vis.resize(nums.size());
sort(nums.begin(),nums.end());
backtrack(nums,ans,0,perm);//一开始肯定对第0个元素操作
return ans;
}
};
最后也附上全排列问题1的代码:
class Solution {
public:
void backtrack( vector<int> & Num,vector<int> & track,vector<vector<int>> & res ) {
if(track.size()==Num.size()){
res.push_back(track);
return;
}
for(int i=0; i<Num.size();i++){
vector<int>::iterator it = find(track.begin(), track.end(), Num[i]);
if(it!=track.end())
continue;
track.push_back(Num[i]);
backtrack(Num,track,res);
track.pop_back();
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> track;
backtrack(nums,track,res);
return res;
}
};
全排列问题1查重我们也可以用visit数组来解决,我们试试看:
class Solution {
vector<int> visit;
public:
void backtrack( vector<int> & Num,vector<int> & track,vector<vector<int>> & res ) {
if(track.size()==Num.size()){
res.push_back(track);
return;
}
for(int i=0; i<Num.size();i++){
//vector<int>::iterator it = find(track.begin(), track.end(), Num[i]);
// if(it!=track.end())
// continue;
if(visit[i])
continue;
track.push_back(Num[i]);
visit[i]=1;
backtrack(Num,track,res);
track.pop_back();
visit[i]=0;
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> track;
visit.resize(nums.size());
backtrack(nums,track,res);
return res;
}
};