1. 程式人生 > >劍指offer——字串的排列(好題,擴充套件題也很好,全排列的演算法)

劍指offer——字串的排列(好題,擴充套件題也很好,全排列的演算法)

題目描述
輸入一個字串,按字典序打印出該字串中字元的所有排列。例如輸入字串abc,則打印出由字元a,b,c所能排列出來的所有字串abc,acb,bac,bca,cab和cba。

輸入描述:
輸入一個字串,長度不超過9(可能有字元重複),字元只包括大小寫字母。

思路:
看題目的意思,應該就是求一個全排列?
想到一個遞迴的方法,先把原字串分割,split(),放入一個char陣列。每次遞迴傳入當前的StringBuilder物件和已使用的下標flag陣列,可以防止重複使用,當物件的length等於字串長度後,轉換成String加入到結果陣列中。

在本地跑是正常的,在OJ系統上提示
測試用例:
a
對應輸出應該為:
[“a”]
你的輸出為:
java.lang.StringIndexOutOfBoundsException: String index out of range: -1

但這個用例本地也是正常的。

查閱相關資料後發現是split的問題。

網上說split的使用盡量都用轉義字元表示。

在java.lang包中有String.split()方法,返回是一個數組:   1、如果用“.”作為分隔的話,必須是如下寫法:String.split("\\."),這樣才能正確的分隔開,不能用String.split("."); 
2、如果用“|”作為分隔的話,必須是如下寫法:String.split("\\|"),這樣才能正確的分隔開,不能用String.split("|"); “.”和“|”都是轉義字元,必須得加"\\";    
3、如果在一個字串中有多個分隔符,可以用“|”作為連字元,比如:“acount=? and
uu =? or n=?”,把三個都分隔出來,可以用String.split("and|or");

這裡只是把String變成字元陣列,還是用自帶的toCharArray()吧。

因為題目中有提到字典序,所以最後還要Collections.sort(result);
這樣即使一開始輸入的字串並不是字典序,最後也會變成字典序。
我的程式碼如下(這個遞迴的思路更好理解一點)

import java.util.ArrayList;
public class Solution {
    ArrayList<String> result = new ArrayList<>();
    public
ArrayList<String> Permutation(String str) { if(str==null||str.length()==0) return result; String[] a = str.trim().split(""); // toCharArray() int length = a.length; int[] flag = new int[length]; StringBuilder sb = new StringBuilder(); helper(sb,flag,a); return result; } public void helper(StringBuilder sb, int[] flag, String[] a){ if(sb.length()==flag.length&&!result.contains(sb.toString())){ result.add(sb.toString()); return; } for(int i = 0; i<flag.length; i++){ if(flag[i]==1) continue; sb.append(a[i]); flag[i] = 1; helper(sb,flag,a); flag[i] = 0; sb.deleteCharAt(sb.length()-1); } } }

解決問題 提交時間 狀態 執行時間 佔用記憶體 使用語言
字串的排列 2017-06-20 答案正確 208 ms 12284K Java

另一種遞迴思路,將元素交換求全排列。
這個方法必須要加Collection.sort(),即使在輸入str是有序的時候也必須進行這一步。
因為一開始是存在了HashSet裡,HashSet裡的元素是無序的。(不保證輸出順序和輸入順序相同)

   public ArrayList<String> Permutation(String str) {
        ArrayList<String> re = new ArrayList<>();
        if (str == null || str.length() == 0) {
            return re;
        }
        HashSet<String> set = new HashSet<String>();
        fun(set, str.toCharArray(), 0);
        re.addAll(set);
        Collections.sort(re);
        return re;
    }
    void fun(HashSet<String> re, char[] str, int k) {
        if (k == str.length) {
            re.add(new String(str));
            return;
        }
        for (int i = k; i < str.length; i++) {
            swap(str, i, k);
            fun(re, str, k + 1);
            swap(str, i, k);
        }
    }
    void swap(char[] str, int i, int j) {
        if (i != j) {
            char t = str[i];
            str[i] = str[j];
            str[j] = t;
        }
    }

非遞迴的生成字典序方法,目前執行時間最少

import java.util.*;
public class Solution{
public ArrayList<String> Permutation(String str) {
       ArrayList<String> res = new ArrayList<>();

        if (str != null && str.length() > 0) {
            char[] seq = str.toCharArray();
            Arrays.sort(seq); //排列
            res.add(String.valueOf(seq)); //先輸出一個解

            int len = seq.length;
            while (true) {
                int p = len - 1, q;
                //從後向前找一個seq[p - 1] < seq[p]
                while (p >= 1 && seq[p - 1] >= seq[p]) --p;
                if (p == 0) break; //已經是“最小”的排列,退出
                //從p向後找最後一個比seq[p]大的數
                q = p; --p;
                while (q < len && seq[q] > seq[p]) q++;
                --q;
                //交換這兩個位置上的值
                swap(seq, q, p);
                //將p之後的序列倒序排列
                reverse(seq, p + 1);
                res.add(String.valueOf(seq));
            }
        }

        return res;
    }

    public static void reverse(char[] seq, int start) {
        int len;
        if(seq == null || (len = seq.length) <= start)
            return;
        for (int i = 0; i < ((len - start) >> 1); i++) {
            int p = start + i, q = len - 1 - i;
            if (p != q)
                swap(seq, p, q);
        }
    }

    public static void swap(char[] cs, int i, int j) {
        char temp = cs[i];
        cs[i] = cs[j];
        cs[j] = temp;
    }
}

如果給的原字串中有重複的字元,雖然用了set結構可以防止重複放入結果集,但是也可以通過程式碼去重,在for迴圈中加入一個判斷即可,如果測試用例中重複字元很多,可以減小時間複雜度


    static boolean is_swap(char[] s, int i, int k)
    {
        if (i == k) // 自己和自己交換是允許的
            return true;
        for (int j = i; j < k; j++) //保證在i到k的中間,不存在和k相同的字元,如果有的話,說明k重複了,這次交換沒有必要
        {
            if (s[j] == s[k])
                return false;
        }
        return true;
    }
return true;
}

擴充套件延伸題:
這裡寫圖片描述

這裡寫圖片描述
這裡寫圖片描述