【LeetCode】Permutations 解題報告
阿新 • • 發佈:2019-01-08
全排列問題。常用的排列生成演算法有序數法、字典序法、換位法(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;
}
歡迎高人對上述程式碼繼續優化!