题目描述
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
给定一个数字的集合,集合中可能包含重复的数字,返回所有可能的唯一的全排列。
Example:
Input: [1,1,2]
Output:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
思路分析
和47. Permutations的大体思路类似,就是采用DFS来求全排列。不同的是,这里的数字有可能重复,因此需要在DFS判断的基础上,在DFS树上进行剪枝操作。
传统DFS的套路是dfs(int[] nums, List<Integer> temp, List<List<Integer>> result)
,其中nums作为输入参数,temp作为DFS树每条路径的临时结果,result作为存放最终结果的输出参数。为了减少空间复杂度,作为临时结果的temp参数可以重用,通过在DFS执行方法后执行remove操作恢复现场。
另外需要考虑的就是该如何剪枝?例如[1,1,2],如果按照不剪枝的方法执行DFS,得到的结果如图所示。
如图的DFS数中,圈子划出的部分就是需要剪枝的部分,经过分析可以发现,需要剪枝的部分都是由于对重复的元素进行了多余的递归操作。而需要判断重复的元素,就需要先对输入数组nums进行排序,在DFS递归之前判断当前nums[i]是否和前面的nums[i-1]相等,并且通过visited[]数组判断DFS树是否是刚把nums[i-1]的处理完退出的情况:
- 如果visited[i-1]==true,说明nums[i-1]没有退出,此时正处于如图所示的位置。
-
如果visited[i-1]==false,说明DFS刚结束了左边的子树,打算以第二个1为基准进行多余的递归,所以开始剪掉。
代码实现
public class Solution {
/**
* 30 / 30 test cases passed.
* Status: Accepted
* Runtime: 9 ms
*
* @param nums
* @return
*/
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length == 0) {
return result;
}
boolean[] visited = new boolean[nums.length];
List<Integer> temp = new ArrayList<>();
Arrays.sort(nums);
permuteRecursively(nums, visited, temp, result);
return result;
}
private void permuteRecursively(int[] nums, boolean[] visited,
List<Integer> temp, List<List<Integer>> result) {
if (temp.size() >= nums.length) {
result.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < nums.length; i++) {
// 前一层递归已经访问过,跳过
if (visited[i]) {
continue;
}
// i和前面的相同,但是前面的没有访问,
// 只能是因为前面的已经退出来了,那这个也就跳过
if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
continue;
}
visited[i] = true;
temp.add(nums[i]);
permuteRecursively(nums, visited, temp, result);
temp.remove(temp.size() - 1);
visited[i] = false;
}
}
}