1. 程式人生 > 其它 >回溯——46.全排列 && 47.全排列 II

回溯——46.全排列 && 47.全排列 II

給定一個 沒有重複 數字的序列,返回其所有可能的全排列。

示例:

  • 輸入: [1,2,3]
  • 輸出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

全排列,顧名思義就是:Amn,結果剛開始的我想都沒想就寫出了空間O(n2)的演算法。。。。

//錯誤示範
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        int n = nums.length;
        int[][] ans = new int[n][n];
        for (int i = 0; i < n; ++i) {
            int number = nums[i];
            int index = i;
            for (int j = 0; j < n; ++j) {
                int[] arr = ans[j];
                arr[index] = number;
                index = (index + 1) % n;
            }
        }
        for (int[] arr : ans) {
            List<Integer> list = new ArrayList<>();
            for (int number : arr) {
                list.add(number);
            }
            result.add(list);
        }
        return result;
    }

根據Anm,本來想用迴圈,填滿一個int[Anm][Anm]的矩陣,再轉化為二維List。

後來用手寫模擬全排列列舉過程的時候,發現了規律,可見手寫模擬的重要性:

  對於當前的path,只要確定了當前需要新增的數字,那麼這條path只要將nums裡剩下的數字的全排列,依次拼接到path中,就完成的單層的回溯任務。

  所以在當前回溯步驟中,對於拿到的陣列nums,我們要依次挑選(遍歷)所有的數字,並將刪除了這個數字的子陣列再傳入下一層回溯樹中,這就確定了參

由此回溯分析完成:

  1. 返回值:void——不需要,由外層的result收集結果
  2. 引數:List<Integer>nums——在當前回溯步驟中,對於拿到的陣列nums,我們要依次挑選(遍歷)所有的數字,並將刪除了這個數字的子陣列
    再傳入下一層回溯樹中
  3. 終止:path.size()== 初始陣列的長度,則result.add();
  4. 單層邏輯:在當前回溯步驟中,對於拿到的陣列nums,我們要依次挑選(遍歷)所有的數字,並將數字加入path中,然後將刪除了這個數字的子陣列再傳入下一層回溯樹中

因此程式碼如下:

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    ArrayList<Integer> path = new ArrayList<>();
    //原始陣列的長度
    int n = 0;
    public List<List<Integer>> permute(int[] arr) {
        n = arr.length;
        //將原始陣列處理成List,方便回溯時刪除元素
        List<Integer> nums = new ArrayList<>(n);
        for (int i : arr) {
            nums.add(i);
        }

        backtracking(nums);
        return result;
    }

    private void backtracking(List<Integer> nums) {
        if (path.size() == n) {
            result.add((ArrayList) path.clone());
            return ;
        }

        int i = 0;
        for (Integer number : nums) {
            path.add(number);
            backtracking(delete(nums, i++));
            path.remove(path.size()-1);
        }
    }

    //用以刪除List中具體索引的數,並返回一個新的list
    private List<Integer> delete(List<Integer> nums, int index) {
        List<Integer> ans = new ArrayList<>();
        int i = 0;
        for (Integer number : nums) {
            if (i++ == index) {
                continue;
            }
            ans.add(number);
        }
        return ans;
    }
}

由此做 47.全排列 II 時,自然先用筆模擬了一下,發現了以下規律:

  在全排列回溯時,當前回溯樹層已經加入path的數不要再次加入,否則會產生重複

程式碼如下:

    private void backtracking(List<Integer> nums){
        if (path.size() == n) {
            result.add((ArrayList) path.clone());
            return ;
        }

        Set<Integer> set = new HashSet<>();
        int i = -1;
        for (Integer number : nums) {
            ++i;
            //如果重複了,則直接跳過
            if (set.contains(number)) {
                continue;
            } else {
                set.add(number);
                path.add(number);
                backtracking(delete(nums, i));
                path.remove(path.size()-1);
            }
        }
    }