1. 程式人生 > >《劍指offer》——字串的排列

《劍指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

這是其所有的排列組合。數學上很好表示: Ann,但是這隻能得到組合數,是一個數值,並不能得到其所有的組合數,這是本程式要乾的事兒。

遞迴方式

這裡寫圖片描述

下面上程式碼:

    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);
            }
        }
    }

第一次出現遞迴時,只用了permutaionIterate(arr,curIndex+1);,指的是在當前狀態下,第一個字母沒有交換的情況,而之後的for迴圈裡面的遞迴呼叫,第一個字母和後面的依次交換,每次交換都得到一組新的組合。

缺陷:

如果數組裡面有重複的情況,那麼得出的全排列中,有部分會出現重複。
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,就可得到按照字典順序排序的全排列。

非遞迴