在leetcode上,跟Permutations有關的題目:
- 31 Next Permutation
- 46 Permutations
一.31 Next Permutation
31題是排列的入門題,給出[1,2,3,4],需給出下一排列[1,2,4,3]。這題有固定的解法,給定排序nums[n]=[1,4,2,7,6,5,3],n=0~6:
- 從序號6開始往前尋找第一對嚴格遞減(即找到第一個小於的數,從後往前看)的兩個數,在這裏是[2,7],記作[i,j],從7→2是嚴格遞減。
- 從序號6開始尋找第一個大於序號i的數2,找到數3序號k,交換數2序號i和數3序號k,得到[1,4,3,7,6,5,2]
- 將從j開始(從7開始)一直到最後的序列改爲正序(此時的序列一定是逆序的),得到[1,4,3,2,5,6,7]
這裏考慮兩種極端情況[1,2,3,4,5,6,7]和[7,6,5,4,3,2,1]。
前一種情況:[i,j]=[6,7],[k]=[7];交換i,k,即6和7;反轉從k開始的序列,這種情況不特殊,可與一般情況的一起處理;
後一種情況:i<0,j=0,直接全體逆序一下即可,這種情況特殊,不能和一般情況一起處理。
二.46 Permutations
本題可以有三種解法:
- 回溯法(此方法也是leetcode上提示的方法)
- 利用31題,只要知道一種排列,後續的都可以next出來
- 使用dfs
2.1 回溯法
回溯法的本質是類似於枚舉的搜索嘗試過程,一般都帶着條件去搜索,如果發現繼續搜索下去也找不到最優解,那麼在此點就開始回溯。一般我們將它的解空間轉化轉化爲樹的形式,這樣便於理解。
這裏我們以排序[1,2,3,4]爲例,畫出它的解空間樹。每當i=4的時候,說明已經找到了一個解。需要尋找下一個解。比如找到了第4層的2314後,這時已經到頭了,我們需要回溯,返回到第3層,再返回到第2層的2314,然後沿着另外一條路到了第4層的2341,這樣就找到了另一個解。
在排列問題中,沒有約束條件,沒有約束條件的回溯有點像暴力窮舉,要達到葉子結點纔會回溯。如要有條件的話,就可以對解空間樹進行剪枝,可以避免許多明顯不必要搜索的路徑。
用遞歸實現的回溯比較簡單易懂,回溯法一般有以下模板:
//用遞歸實現回溯的一般模板
void backTrack(int i) {
if(i > n) {
//到達葉子結點,分析此解是否最優
return;
}
for(int k=low; k<high; k++) {
if(fx()) {//滿足約束條件
a += nums[i];
backTrack(i+1);
a -= nums[i];//在回溯前進行狀態的清零
}
}
}
有許多經典的問題都可以用回溯法來解決,比如8皇后問題、01揹包問題等。
迴歸這道題目,下面給出這道題回溯解法。
public class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> arrAll = new ArrayList<List<Integer>>();
backTrack(0, nums, arrAll);
return arrAll;
}
private void backTrack(int i, int[] nums, List<List<Integer>> arrAll) {
if(i>=nums.length) {
List<Integer> arr = new ArrayList<Integer>();
for (int a : nums) {
arr.add(a);
}
arrAll.add(arr);
return;
}
for(int k=i; k<nums.length; k++) {
exch(nums, i, k);
backTrack(i+1, nums, arrAll);
exch(nums, i, k);
}
}
private void exch(int[] nums, int i, int k) {
int temp = nums[i];
nums[i] = nums[k];
nums[k] = temp;
}
}
2.2 next法
用現成的next法
2.3 dfs法
【Reference】
46 Permutations三種不同的解法 https://leetcode.com/discuss/20474/share-my-three-different-solutions