问题:
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] ]
解决:
① 与Permutations类似,因为包含重复的数字,所以我们要对重复的排列序列进行排除,首先我们对数组进行排序,判断当前遍历到的数是否与前一个数相同,使用一个标记数组boolean[] used来判断前一个相同的值是否被使用了,若正在被使用,说明正在处于前一个值得递归过程中,当前值不能被跳过;若没有被使用,说明这个值已经作为头部被使用过了,跳过当前值以排除重复。使用一个链表pre来记录之前排列好的值,需要注意的是,遍历完成一个排列之后,需要还原pre以进行下一次排列。
public class Solution { //7ms
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(nums == null || nums.length == 0) return res;
boolean [] used = new boolean[nums.length];
List<Integer> pre = new ArrayList<>();
Arrays.sort(nums);
dfs(nums,used,pre, res);
return res;
}
private void dfs(int[] nums, boolean [] used, List<Integer> pre, List<List<Integer>> res){
if(pre.size() == nums.length){
res.add(new ArrayList<>(pre));//若直接res.add(pre),会导致集合为空[[],[],[],[],[],[]]
return;
}
for(int i = 0; i < nums.length; i ++){
if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;//去重
if(! used[i]){
used[i] = true;
pre.add(nums[i]);//加入当前值
dfs(nums,used,pre,res);//递归查找值
pre.remove(pre.size() - 1);//还原前一个排列,继续查找排列
used[i] = false;
}
}
}
}
② 另外,我们可以使用一个临时链表cur来记录加入当前值之后的排列,这样,就可以避免还原pre。
public class Solution { //11ms
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
int len = nums.length;
if(len == 0) return res;
Arrays.sort(nums);
boolean[] used = new boolean[len];
List<Integer> pre = new ArrayList<>();
dfs(nums,used,pre);
return res;
}
private void dfs(int[] nums,boolean[] used,List<Integer> pre){
if(pre.size() == nums.length){//完成一个排列
//res.add(new ArrayList<Integer>(pre));
res.add(pre);
return;
}
for(int i = 0;i < nums.length;i ++){
if(i > 0 && nums[i] == nums[i - 1] && ! used[i - 1]) continue;//前一个数没有被选中,说明在前一个数时已经完成了以该数为开头的排列
List<Integer> cur = new ArrayList<Integer>(pre);//这样就不需要还原pre了,因为pre与cur分开了
if(! used[i]){
cur.add(nums[i]);
used[i] = true;
dfs(nums,used,cur);
used[i] = false;
}
}
}
}
③ 直接使用backtrack(DFS)而不用使用标记位。并且不需要对数组排序。以{1,1,2}为例进行递归分析如下:
class Solution { //6ms
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
dfs(nums,0,res);
return res;
}
public void dfs(int[] nums,int i,List<List<Integer>> res){//i表示当前排列的开头在原固定数组中的位置
//找到转换完成的链表,将其存入链表中
if(i == nums.length - 1){//递归结束条件,得到一个排列
List<Integer> tmp = new ArrayList<>();
for (int j = 0;j < nums.length ;j ++ ) {
tmp.add(nums[j]);
}
res.add(tmp);
}else{//没有完成排列
for (int j = i;j < nums.length ;j ++ ){//排列i之后的数
if(containsRepeated(nums,i,j)) continue;//判断i到j之间是否存在重复,如果存在,则不需要交换和递归,因为这是同一个排列
//若不存在重复,则进行以下排列
swap(nums,i,j); //交换开头,若j=i表示固定当前开头计算排列,否则表示以当前值为开头的已经排列完了
dfs(nums,i + 1,res);//改变数组的开头为原数组中i之后的第一个数,递归得到它的全排列
swap(nums,i,j);//递归完成,还原交换的数组
}
}
}
public boolean containsRepeated(int[] nums,int i,int j){
for (int k = i;k <= j - 1 ;k ++ ) {//若i=j,则无法进入该循环,直接返回false;
if(nums[k] == nums[j]) return true;
}
return false;
}
public void swap(int[] nums,int i,int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
转载于:https://my.oschina.net/liyurong/blog/1528741