[leetcode/lintcode 題解] 微軟面試題:帶重複元素的排列

給出一個具備重複數字的列表,找出列表全部不一樣的排列。
 
在線評測地址:領釦題庫官網
 
 
樣例 1:
輸入:[1,1]
輸出:
[
  [1,1]
]
樣例 2:
輸入:[1,2,2]
輸出:
[
  [1,2,2],
  [2,1,2],
  [2,2,1]
]
 
解題思路
  • 這道題咱們須要使用dfs+回溯的方法來進行求解。
  • 咱們定義dfs函數,使用遞歸的方法對決策樹進行深度優先遍歷。對於長度爲n的數組nums,咱們一位一位地生成它的排列數組,每深刻一層數組長度就加1,遍歷到葉節點時生成數組的長度達到n,即爲咱們的答案。
  • 因爲數組中有重複元素,因此咱們在遍歷時須要剪枝操做。
算法流程
  • 首先對數組進行排序,以使得重複元素相鄰,這樣才能進行剪枝。
  • 定義數組usedused[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'用於區分相同元素。每一個節點有pathused兩個屬性。
  • 首先,在根結點,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
        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();
        }
    }
}
 
更多題解參考:九章官網solution
相關文章
相關標籤/搜索