在解決全排列問題之前,先講解一個它的子問題,輸出給定數字比它大的下一個數字,為了方便,我們把輸入和輸出數字用數組表示
給出一組數,輸出它的下一個排列
假設給定數組[1,2,3,5,4] 比它大的下一個數字是[1,2,4,3,5]
思想:
假設出入數組為arr
1.我們從后向前遍歷,找到arr[i]>arr[i-1],並且返回i,如果沒有找到返回0;
2.用index接收返回的下標,判斷index是否等於0,如果等於0,返回null,否則在區間 arr.length-1 和 index 區間找出比arr[index-1]小的值,兩者交換,並退出循環;
3.在對 index和arr.length-1組成的區間 進行逆序,
4.返回數組arr,數組arr表示當前數組的下一個排列
實現圖如下:
實現代碼如下:
//找到下一個排列
public static int[] findNearestNumber(int[] arr){ int index = returnIndex(arr); if(index == 0) return null; for(int i = arr.length-1;i>=index;i--){ if(arr[index-1]<arr[i]){ int tmp = arr[index-1]; arr[index-1] = arr[i]; arr[i] = tmp; break; } } reverseArr(arr,index,arr.length-1); return arr; } //逆序
public static void reverseArr(int[] arr,int start,int end){ while(start<=end){ int tmp = arr[start]; arr[start++] = arr[end]; arr[end--] = tmp; } } //從后向前遍歷找出arr[i]<arr[i-1]的下標i
public static int returnIndex(int[] arr){ for (int i = arr.length-1; i >0 ; i--) { if(arr[i]>arr[i-1]){ return i; } } return 0; }
這就是求一個數組的下一個排列數組,是不是很簡單?
下面這個全排列的例子該如何實現呢?
給定一個沒有重復數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
通過上面的例子,我們可以求出一個數組的比它大的下一個數組,那么我們要輸出給定某數組的全部排列就很簡單了,在解決此題之前,我們首先的判斷該數組總共有多少排列組合,然后在進行操作,先對該數組按升序進行排序,也就是最小值a,我們可以求a的下一個排列b,緊接着我們可以求b的下一個排列c...直到該排列為空,我們就求出了該數組的全部排列組合。
代碼如下:
public List<List<Integer>> permute(int[] nums) { Arrays.sort(nums); int length = nums.length; List<List<Integer>> list = new ArrayList<List<Integer>>(); int n = getN(length); for(int i = 0;i<n;i++){ List<Integer> list1 = new ArrayList<Integer>();
//將原數組加入集合中 if(i == 0){ List<Integer> list2 = new ArrayList<Integer>(); for(int j:nums){ list2.add(j); } list.add(list2); }
//求得nums下一個排列 nums = findNearestNumber(nums);
//如果返回數組為null表示已經排列完成,結束循環 if(nums == null) break;
//將數組元素加入集合中 for(int j:nums){ list1.add(j); }
//將集合list1加入集合list中 list.add(list1); } return list; } //獲取總共的排列組合數
public static int getN(int length){ if(length == 1){ return length; } return length*getN(length-1); } //排列
public static int[] findNearestNumber(int[] arr){ int index = returnIndex(arr); if(index == 0) return null; for(int i = arr.length-1;i>=index;i--){ if(arr[index-1]<arr[i]){ int tmp = arr[index-1]; arr[index-1] = arr[i]; arr[i] = tmp; break; } } reverseArr(arr,index,arr.length-1); return arr; } //逆序
public static void reverseArr(int[] arr,int start,int end){ while(start<=end){ int tmp = arr[start]; arr[start++] = arr[end]; arr[end--] = tmp; } } //從后向前遍歷找出arr[i]<arr[i-1]的下標i
public static int returnIndex(int[] arr){ for (int i = arr.length-1; i >0 ; i--) { if(arr[i]>arr[i-1]){ return i; } } return 0; }
如果給定的數組有重復的數字,那么我們該如何解決呢?
其實上面的代碼也適用於數組有重復數字,這就是采用非遞歸的好處。
下面是采用遞歸實現代碼:
這個代碼實現給定的數組中沒有重復數字的
//遞歸實現 調用時n是0
public static void printSort(int[] num, int n) {
if(n >= num.length-1){
System.out.println(Arrays.toString(num));
}
else{
for(int i = n;i < num.length;i++) {
swap(num,i,n);
printSort(num,n+1);
swap(num,i,n);
}
}
}
//交換指定下標元素
public static void swap(int[] num, int i, int n) {
int flag = num[i];
num[i] = num[n];
num[n] = flag;
}
如果有重復的元素,用遞歸該如何實現,其實很簡單,我們只要讓第一次出現該數作為開頭
代碼如下:
//調用時n是0
public static void printSort(int[] num, int n) {
List<Integer> list = new ArrayList<Integer>();
if(n >= num.length-1){
System.out.println(Arrays.toString(num));
}
else{
for(int i = n;i < num.length;i++) {
//篩選重復的數
if(!list.contains(num[i])){
list.add(num[i]);
swap(num,i,n);
printSort(num,n+1);
swap(num,i,n);
}
}
}
}
public static void swap(int[] num, int i, int n) {
int flag = num[i];
num[i] = num[n];
num[n] = flag;
}