1. 程式人生 > >【LeetCode】Permutations 解題報告

【LeetCode】Permutations 解題報告

全排列問題。常用的排列生成演算法有序數法、字典序法、換位法(Johnson(Johnson-Trotter)、輪轉法以及Shift cursor cursor* (Gao & Wang)法。

【題目】

Given a collection of numbers, return all possible permutations.

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

【暴力遞迴】

這是比較直觀的思路。但是也有要注意的地方,剛開始寫的時候,沒有注意到list是共用的,所以前面得到的答案後面會改掉而導致錯誤。

public class Solution {
    List<List<Integer>> ret = new ArrayList<List<Integer>>();
    
    public List<List<Integer>> permute(int[] num) {
        int len = num.length;
        if (len == 0) return ret;
        
        List<Integer> list = new ArrayList<Integer>();
        run(list, num);
        return ret;
    }
    
    public void run(List<Integer> list, int[] num) {
        if (list.size() == num.length) {
            //注意這裡要重新new一個list,要不然後面會被修改
            List<Integer> res = new ArrayList<Integer>();
            res.addAll(list);
            ret.add(res);
            return;
        }
        for (int i = 0; i < num.length; i++) {
            if (list.contains(num[i])) {
                continue;
            }
            list.add(num[i]);
            run(list, num);
            list.remove(list.indexOf(num[i])); //不要忘記這一步
        }
    }
}

【字典序法】

C++的STL庫裡面有nextPermutation()方法,其實現就是字典序法。

下圖簡單明瞭地介紹了字典序法


歸納一下為:


例如,1234的全排列如下:


【程式碼實現】

由於Java的list傳參傳的是地址,所以每次新增時都要記得重新new一個新的list新增到結果集中,否則新增到結果集中的原list會被後面的操作改變。

public class Solution {
    public List<List<Integer>> permute(int[] num) {
        List<List<Integer>> ret = new ArrayList<List<Integer>>();
        
        int len = num.length;
        if (len == 0) return ret;
        
        Arrays.sort(num); //字典序法需先對陣列升序排序
        
        //陣列轉為list
        List<Integer> list0 = new ArrayList<Integer>();
        for (int i = 0; i < len; i++) {
            list0.add(num[i]);
        }
        
        //把原始陣列對應的list新增到結果中,不能直接新增list0,因為後面它會一直變化
        List<Integer> ll = new ArrayList<Integer>();
        ll.addAll(list0);
        ret.add(ll);
        
        //逐次找下一個排列
        for (int i = 1; i < factorial(len); i++) {
        	ret.add(nextPermutation(list0));
        }
        return ret;
    }
    
    /***字典序法生成下一個排列***/
    public List<Integer> nextPermutation(List<Integer> num) {
        //找到最後一個正序
        int i = num.size()-1;
        while(i > 0 && num.get(i-1) >= num.get(i)){  
            i--;  
        }
        
        //找到最後一個比num[i-1]大的數
        int j = i;  
        while(j < num.size() && num.get(j) > num.get(i-1)) {
            j++;
        }
        
        //交換num[i-1]和num[j-1]
        int tmp = num.get(i - 1);
        num.set(i - 1, num.get(j - 1));
        num.set(j - 1, tmp);
        
        //反轉i以後的數
        reverse(num, i, num.size()-1);
        
        List<Integer> ret = new ArrayList<Integer>();
        ret.addAll(num);
        return ret;
    }
    
    public void reverse(List<Integer> list, int begin, int end) {
        for(int i = begin, j = end; i < j; i++) {
            list.add(i, list.remove(j));
        }
    }
        
    public int factorial(int n) {
        return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
    }
}

上面的實現需要先對原陣列升序排序,下面對nextPermutation(List<Integer> num)改進後就不用對num排序了。

    /***字典序法生成下一個排列***/
    public List<Integer> nextPermutation(List<Integer> num) {
        //找到最後一個正序
        int i = num.size()-1;
        while(i > 0 && num.get(i-1) >= num.get(i)){  
            i--;  
        }
        
        //有了這個判斷就不用num最初是按升序排好的了
        if (i == 0) {
            reverse(num, 0, num.size()-1);
            List<Integer> ret = new ArrayList<Integer>();
            ret.addAll(num);
            return ret;
        }
        
        //找到最後一個比num[i-1]大的數
        int j = i;  
        while(j < num.size() && num.get(j) > num.get(i-1)) {
            j++;
        }
        
        //交換num[i-1]和num[j-1]
        int tmp = num.get(i - 1);
        num.set(i - 1, num.get(j - 1));
        num.set(j - 1, tmp);
        
        //反轉i以後的數
        reverse(num, i, num.size()-1);
        
        List<Integer> ret = new ArrayList<Integer>();
        ret.addAll(num);
        return ret;
    }

歡迎高人對上述程式碼繼續優化!