給出一個具備重複數字的列表,找出列表全部不一樣的排列。
樣例 1:
輸入:[1,1]
輸出:
[
[1,1]
]
樣例 2:
輸入:[1,2,2]
輸出:
[
[1,2,2],
[2,1,2],
[2,2,1]
]
解題思路
- 這道題咱們須要使用dfs+回溯的方法來進行求解。
- 咱們定義dfs函數,使用遞歸的方法對決策樹進行深度優先遍歷。對於長度爲n的數組nums,咱們一位一位地生成它的排列數組,每深刻一層數組長度就加1,遍歷到葉節點時生成數組的長度達到n,即爲咱們的答案。
- 因爲數組中有重複元素,因此咱們在遍歷時須要剪枝操做。
算法流程
- 首先對數組進行排序,以使得重複元素相鄰,這樣才能進行剪枝。
- 定義數組used,used[i]表示nums[i]是否已使用過,初始化全爲false。數組path,表示從根結點到該節點通過的路徑,即當前已生成的數組,初始化爲空。數組res存儲結果。
- 使用dfs函數進行遞歸遍歷
- 遞歸出口:若是path的長度與nums的長度相等,說明已經生成好了排列數組path,那麼咱們把它的拷貝加入res中。
- 遍歷nums中的每一個元素,對於nums[i]
- 若是path中已經存在,即used[i]爲true,跳過
- 若是它和前一位元素相等,即nums[i-1] == nums[i],而且前一位元素已經搜索並回溯過了,即!used[i-1],爲了不生成重複的排列數組,也跳過
- 排除上述兩種狀況後,把nums[i]變爲true,而後對新生成的path繼續送入dfs函數中。
- 最後進行回溯操做,即刪除path[i],used[i]變爲false。
舉例說明
- 如圖所示,nums = [1, 2, 2],第二個2標記爲2'用於區分相同元素。每一個節點有path和used兩個屬性。
- 首先,在根結點,path爲[],used全爲false(圖中標爲[0, 0, 0])。而後進行dfs遍歷,到下一層,先加入元素1,path爲[1],used爲[1, 0,0 ]。再到下一層,因爲1已經使用過了,咱們加入元素2,path爲[1, 2],used爲[1, 1,0 ]。這樣,每深一層path長度加1。達到最底層的葉節點,path爲[1, 2, 2],把它加入res中。同理,能夠獲得其餘的葉節點。
- 注意,圖中標出畫叉的地方,表明出現了重複元素而進行剪枝。
複雜度分析
- 時間複雜度:O(n×n!),這裏 n 爲數組的長度。當沒有重複元素時,排列數組有n!個,即最深層有n!個葉子節點,而拷貝操做須要n,因此時間複雜度爲O(n×n!)
- 空間複雜度:O(n×n!)。最差狀況下,返回的全排列數組有n!個,每一個長度爲n。
代碼
public class Solution {
* @param : A list of integers
* @return: A list of unique permutations
*/
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
boolean[] used = new boolean[nums.length];
Deque<Integer> path = new ArrayDeque<>(nums.length);
Arrays.sort(nums);
dfs(nums, used, path, res);
return res;
}
private void dfs(int[] nums, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; ++i) {
if ((used[i]) || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) {
continue;
}
path.addLast(nums[i]);
used[i] = true;
dfs(nums, used, path, res);
used[i] = false;
path.removeLast();
}
}
}