劍指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;
}
擴充套件延伸題: