题目描述
Given a collection of numbers, nums, that might contain duplicates, return all possible unique permutations in any order.
样例描述
Example 1:
Input: nums = [1,1,2]
Output:
[[1,1,2],
[1,2,1],
[2,1,1]]
Example 2:
Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Constraints:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
思路
- 这题不能直接暴力dfs,因为可能存在重复元素,例如
1,1',2
和1',1,2
,其实是同一种排列方式。(这里用1,1’ 人为区分两个相同元素)。所以需要进行剪枝。剪枝的前提是排序,必须先对原数组排序。 - 为了不出现上面的重复排列,规定1’都必须在1的后面出现,否则肯定有重复。即我们对1’开头的排列全部剪枝了。 具体体现到代码中就是,如果排序后的数组中存在俩个相同的数,由于我们是按序遍历,这俩数肯定是相邻,规定要是前面那个没有使用过的话,后面的就不应该出现,就直接
continue
。
保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」 - 回溯时一定记得恢复递归前的状态。
代码
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
int len=nums.length;
List<List<Integer>> ans=new ArrayList<>();
//排序是剪枝的前提
Arrays.sort(nums);
Deque<Integer> path=new ArrayDeque<>(len);
//状态数组,表示数字是否被使用过
boolean[] used=new boolean[len];
dfs(nums,len,0,ans,path,used);
return ans;
}
void dfs(int[] nums,int len,int cur,List<List<Integer>> ans,Deque<Integer> path,boolean[] used){
//如果当前cur等于长度,表明形成了一个排序,加入到结果集中
if(cur==len){
ans.add(new ArrayList<>(path));
return;
}
//依次遍历每个数字
for(int i=0;i<len;i++){
//若该数没有被使用过
if(!used[i]){
//若有俩数相等,且前面那个没出现过,则后面的不能出现
if(i>0&&nums[i]==nums[i-1]&&!used[i-1])
continue;
used[i]=true;
path.addLast(nums[i]);
dfs(nums,len,cur+1,ans,path,used);
//回溯法一定要记得恢复递归前的状态 (其实就是dfs前面的代码的对称写法)
used[i]=false;
path.removeLast();
}
}
}
}