全排列 Permutations

問題:算法

Given a collection of distinct numbers, return all possible permutations.數組

For example,
[1,2,3] have the following permutations:spa

[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

解決:設計

【注】全排列的實現,以123爲例:code

① 題目要求列出數組的全排列。使用遞歸(回溯)方法。用DFS,時間 O(N^2) 空間 O(N)。遞歸

以[1,3,5,9]的全排列爲例:rem

第一個:1,3,5,9.(保存不變)
首先保持1不變,對3,5,9 進行全排列。一樣地,咱們先保持3不變,對5,9 進行全排列。保持5不變,對9對進行全排列,因爲9只有一個,它的排列只有一種:9
接下來不能以5 打頭了,5,9 相互交換,獲得
1,3,9,5.
此時5,9 的狀況都寫完了,不能以3打頭了,獲得
1,5,3,9
1,5,9,3
1,9,3,5
1,9,5,3io

咱們就獲得了1開頭的全部排列,這是咱們通常的排列數生成的過程。再接着是以三、五、9 打頭,獲得全排列這裏還要注意的一點是,對於咱們人而言,咱們腦子裏至關因而儲存了一張表示原有數組的表,1,3,5,9,1 開頭的全部排列完成後,咱們選擇3 開頭,3 選完了以後,咱們選擇5 開頭,而不會再返過來選1,並且知道選到9 以後結束,但對於計算機而言,咱們獲得了3,5,1,9 後,可能再次跳到1 當中,由於原來數組的順序它已經不知道了,這樣便產生了錯誤對於算法的設計,咱們也能夠維護這樣一個數組,它保
存了原始的數據,這是一種方法
。同時咱們還能夠再每次交換後再交換回來,變回原來的數組,這樣程序在遍歷的時候便不會出錯for循環

以上方法能夠總結爲:任意選一個數(通常從小到大或者從左到右)打頭,對後面的n-1 個數進行全排列class

這是一個遞歸的方法,由於要獲得n-1 個數的全排列,咱們又要先去獲得n-2 個數的全排列,而出口是隻有1 個數的全排列,由於它只有1 種,爲它的自己。

寫成比較規範的流程:

1.開始for 循環。
2.改變第一個元素爲原始數組的第一個元素(什麼都沒作)。
3.求第2個元素到第n個元素的全排列。
4.要求第2個元素到第n個元素的全排列,要遞歸的求第3個元素到第n個元素的全排列。
......
5.直到遞歸到第n個元素到第n元素的全排列,遞歸出口。
6.將改變的數組變回
7.改變第一個元素爲原始數組的第二個元素
5.求第2個元素到第n個元素的全排列。
6.要求第2個元素到第n個元素的全排列,要遞歸的求第3 個元素到第n個元素的全排列。
......
5.直到遞歸到第n 個元素到第n 元素的全排列,遞歸出口。
6.將改變的數組變回。
......
8.不斷地改變第一個元素,直至n次使for循環停止

class Solution { //5ms
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        dfs(nums,0,res);
        return res;
    }
    public void dfs(int[] nums,int i,List<List<Integer>> res){//i表示當前排列的開頭在原固定數組中的位置,便是否交換到了最後一個位置
        //找到轉換完成的鏈表,將其存入鏈表中
        if(i == nums.length - 1){//遞歸結束條件,獲得一個排列
            List<Integer> tmp = new ArrayList<>();
            for (int j = 0;j < nums.length ;j ++ ) {
                tmp.add(nums[j]);
            }
            res.add(tmp);
        }
        // 將當前位置的數跟後面的數交換,並搜索解
        for (int j = i;j < nums.length ;j ++ ) {
            swap(nums,i,j); //交換開頭,若j=i表示固定當前開頭計算排列,不然表示以當前值爲開頭的已經排列完了
            dfs(nums,i + 1,res);//改變數組的開頭爲原數組中i以後的第一個數,遞歸獲得它的全排列
            swap(nums,i,j);//遞歸完成,還原交換的數組

        }
    }
    public void swap(int[] nums,int i,int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

② 咱們還能夠簡單的使用DFS來解決這題。使用一個專門的鏈表pre記錄當前遍歷到的排列,每一輪搜索選擇一個數加入鏈表中,同時咱們還要維護一個全局的布爾數組,來標記哪些元素已經被加入鏈表了,這樣在下一輪搜索中要跳過這些元素。時間 O(N) 空間 O(N) 

class Solution { //6ms
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> pre = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        dfs(nums,used,pre,res);
        return res;
    }
    public void dfs(int[] nums,boolean[] used,List<Integer> pre,List<List<Integer>> res){
        if(pre.size() == nums.length){
            res.add(new ArrayList<>(pre));
            return;
        }
        for (int i = 0;i < nums.length;i ++ ) {
            if(! used[i]){//若當前值沒有被加入過,加入到鏈表中
                pre.add(nums[i]);
                used[i] = true;
                dfs(nums,used,pre,res);
                pre.remove(pre.size() - 1);                 used[i] = false;             }         }     } }

相關文章
相關標籤/搜索