《劍指offer》——字串的排列
T:
題目描述
輸入一個字串,按字典序打印出該字串中字元的所有排列。例如輸入字串abc,則打印出由字元a,b,c所能排列出來的所有字串abc,acb,bac,bca,cab和cba。 結果請按字母順序輸出。輸入描述:
輸入一個字串,長度不超過9(可能有字元重複),字元只包括大小寫字母。
這個題目雖然是在說按照字典順序列印所有的字串,抽象來看,就是一個全排列問題。
比如說,給一個序列:
1, 6, 3
那麼其所有的組合為:
- 1, 6, 3
- 1, 3, 6
- 6, 1, 3
- 6, 3, 1
- 3, 6, 1
- 3, 1, 6
這是其所有的排列組合。數學上很好表示:
遞迴方式
下面上程式碼:
public class Solution {
private ArrayList<String> results = new ArrayList<String>();
public ArrayList<String> Permutation(String str) {
if (str.isEmpty() || str == null ) {
return results;
}
char []arr = str.toCharArray();
this.permutaionIterate(arr, 0);
Collections.sort(results);
return results;
}
/**
* 遞迴函式
* @param arr 整個陣列
* @param curIndex 當前的指標位置
*/
public void permutaionIterate (char []arr, int curIndex) {
if (curIndex == arr.length - 1) {
results.add(new String(arr));
return;
}
permutaionIterate(arr, curIndex + 1);
for (int i = curIndex + 1; i < arr.length; i++) {
swap(arr, curIndex, i);
permutaionIterate(arr, curIndex + 1);
swap(arr, curIndex, i);
}
}
public void swap(char []arr, int i, int j) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String []args) {
char []arr = {};
Solution solution = new Solution();
String str = new String(arr);
// System.out.println(str);
solution.Permutation(str);
for (String s : solution.results) {
System.out.println(s);
}
}
最重要的是其中的遞迴這一部分:
/**
* 遞迴函式
* @param arr 整個陣列
* @param curIndex 當前的指標位置
*/
public void permutaionIterate(char []arr, int curIndex) {
if (curIndex == arr.length - 1) {
results.add(new String(arr));
return;
}
permutaionIterate(arr, curIndex + 1);
for (int i = curIndex + 1; i < arr.length; i++) {
if (!isEqual(arr, curIndex, i)) {
swap(arr, curIndex, i);
permutaionIterate(arr, curIndex + 1);
swap(arr, curIndex, i);
}
}
}
第一次出現遞迴時,只用了
缺陷:
如果數組裡面有重複的情況,那麼得出的全排列中,有部分會出現重複。
eg:1,2,2
其組合也只有如下三種:
1, 2, 2
2, 1, 2
2, 2, 1
其中,對於陣列中重複的情況,可分為以下兩種:
- 當前狀態中,第一個字母和後面的有重複,比如: 4, 3, 4, 5, …
這種情況,第一個4和後面的那個4就沒必要交換,換之後還是一樣,而如果不對程式進行改進,其還是會把換之後的情況當做一個新的組合來處理。
如下圖:
- 當前狀態中,框框裡面的有重複。 比如:4, 3, 5, 3, …
在4與第一個3進行交換就可以了,若再與後面的3進行交換,那麼這兩次交換時,框框裡面的內容完全一樣,也會造成重複。
如下圖:
解決辦法就是加判斷:判斷是否有相等的情況。
上關鍵code:
/**
* 遞迴函式
* @param arr 整個陣列
* @param curIndex 當前的指標位置
*/
public void permutaionIterate(char []arr, int curIndex) {
if (curIndex == arr.length - 1) {
results.add(new String(arr));
return;
}
permutaionIterate(arr, curIndex + 1);
for (int i = curIndex + 1; i < arr.length; i++) {
if (!isEqual(arr, curIndex, i)) {
swap(arr, curIndex, i);
permutaionIterate(arr, curIndex + 1);
swap(arr, curIndex, i);
}
}
}
/**
* 是否有數值相等
* @param arr
* @param begin
* @param end
* @return
*/
public boolean isEqual(char []arr, int begin, int end) {
boolean flag = false;
for (int i = begin; i < end; i++) {
if (arr[i] == arr[end]) {
flag = true;
}
}
return flag;
}
注意:
這裡的程式碼中,加入了
Collections.sort() 函式,這是對陣列進行字典排序,至於字典順序是什麼規則,具體的不清楚,執行案例來看,所有大寫字母都要在小寫字母的前面。
陣列:e, F, g
其字典順序如下:
Feg
Fge
eFg
egF
gFe
geF
還一點,這個題目比較坑的是,沒告知在陣列為空的情況下,返回什麼值。。。
DFS搜尋樹結構
這是受該題討論版上的一個人的啟發,他是用dfs在一個數組上模擬dfs的樹訪問形式,那麼程式碼很簡短,但看了半天沒有看懂,反倒給我一種結題的方式,何不構造一個真正的樹結構,然後進行dfs的遍歷呢?
以陣列{a, b, c, d}為例:
構造這樣一個樹結構,是很好構造的。
規律很容易掌握:對於每個節點來說,其子節點都是其剩餘的兄弟節點。
若要求按照字典順序輸出排列組合,可在構造樹之前,對陣列提前進行字典排序,然後對樹結構從左到右進行dfs,就可得到按照字典順序排序的全排列。