輸入一個字符串,按字典序打印出該字符串中字符的全部排列。例如輸入字符串 abc,則打印出由字符 a, b, c 所能排列出來的全部字符串 abc, acb, bac, bca, cab 和 cba。html
通常思路:java
正常人的思惟是,固定第一個字符,而後依次將後面的字符串與前面的交換,那麼排列的個數就是除了第一個字符之外,其餘字符的排列個數+1。算法
也就是固定一個字符串以後,以後再將問題變小,只需求出後面子串的排列個數就能夠得出結果,固然第一時間想到的就是遞歸的算法了。數組
下面這張圖很清楚的給出了遞歸的過程:函數
固定第一個位置,調換後面的位置。spa
很明顯,遞歸的出口,就是隻剩一個字符的時候,遞歸的循環過程,就是從每一個子串的第二個字符開始依次與第一個字符交換,而後繼續處理子串。code
還有一個問題要注意,就是若是字符串中有重複的字符串。htm
因爲全排列就是從第一個數字起,每一個數分別與它後面的數字交換,咱們先嚐試加個這樣的判斷——若是一個數與後面的數字相同那麼這兩個數就不交換 了。例如 abb,第一個數與後面兩個數交換得 bab,bba。而後 abb 中第二個數和第三個數相同,就不用交換了。可是對 bab,第二個數和第三個數不一樣,則須要交換,獲得 bba。因爲這裏的bba和開始第一個數與第三個數交換的結果相同了,所以這個方法不行。blog
換種思惟,對abb,第一個數a與第二個數b交換獲得bab,而後考慮第一個數與第三個數交換,此時因爲第三個數等於第二個數,因此第一個數就再也不用與第三個數交換了。再考慮bab,它的第二個數與第三個數交換能夠解決bba。此時全排列生成完畢!遞歸
這樣,咱們獲得在全排列中去掉重複的規則:
去重的全排列就是從第一個數字起,每一個數分別與它後面非重複出現的數字交換。
/** * 一、遞歸算法 * * 解析:http://www.cnblogs.com/cxjchen/p/3932949.html (感謝該文做者!) * * 對於無重複值的狀況 * * 固定第一個字符,遞歸取得首位後面的各類字符串組合; * 再把第一個字符與後面每個字符交換,並一樣遞歸得到首位後面的字符串組合; *遞歸的出口,就是隻剩一個字符的時候,遞歸的循環過程,就是從每一個子串的第二個字符開始依次與第一個字符交換,而後繼續處理子串。 * * 假若有重複值呢? * *因爲全排列就是從第一個數字起,每一個數分別與它後面的數字交換,咱們先嚐試加個這樣的判斷——若是一個數與後面的數字相同那麼這兩個數就不交換了。 * 例如abb,第一個數與後面兩個數交換得bab,bba。而後abb中第二個數和第三個數相同,就不用交換了。 * 可是對bab,第二個數和第三個數不 同,則須要交換,獲得bba。 * 因爲這裏的bba和開始第一個數與第三個數交換的結果相同了,所以這個方法不行。 * * 換種思惟,對abb,第一個數a與第二個數b交換獲得bab,而後考慮第一個數與第三個數交換,此時因爲第三個數等於第二個數, * 因此第一個數就再也不用與第三個數交換了。再考慮bab,它的第二個數與第三個數交換能夠解決bba。此時全排列生成完畢! * * * @param str * @return */ public ArrayList<String> Permutation(String str){ ArrayList<String> list = new ArrayList<String>(); if(str!=null && str.length()>0){ //只要字符串不爲空同時長度>0 PermutationHelper(str.toCharArray(),0,list); Collections.sort(list); } return list; } private void PermutationHelper(char[] chars,int i,ArrayList<String> list){ if(i == chars.length-1){ list.add(String.valueOf(chars)); }else{ Set<Character> charSet = new HashSet<Character>(); for(int j=i;j<chars.length;++j){ if(j==i || !charSet.contains(chars[j])){ charSet.add(chars[j]); swap(chars,i,j); PermutationHelper(chars,i+1,list); swap(chars,j,i); } } } } private void swap(char[] cs,int i,int j){ char temp = cs[i]; cs[i] = cs[j]; cs[j] = temp; }
import java.util.ArrayList; import java.util.List; import java.util.Collections; public class Solution { public ArrayList<String> Permutation(String str) { List<String> resultList = new ArrayList<>(); if(str.length() == 0) return (ArrayList)resultList; //遞歸的初始值爲(str數組,空的list,初始下標0) fun(str.toCharArray(),resultList,0); Collections.sort(resultList); return (ArrayList)resultList; } private void fun(char[] ch,List<String> list,int i){ //這是遞歸的終止條件,就是i下標已經移到char數組的末尾的時候,考慮添加這一組字符串進入結果集中 if(i == ch.length-1){ //判斷一下是否重複 if(!list.contains(new String(ch))){ list.add(new String(ch)); return; } }else{ //這一段就是回溯法,這裏以"abc"爲例 //遞歸的思想與棧的入棧和出棧是同樣的,某一個狀態遇到return結束了以後,會回到被調用的地方繼續執行 //1.第一次進到這裏是ch=['a','b','c'],list=[],i=0,我稱爲 狀態A ,即初始狀態 //那麼j=0,swap(ch,0,0),就是['a','b','c'],進入遞歸,本身調本身,只是i爲1,交換(0,0)位置以後的狀態我稱爲 狀態B //i不等於2,來到這裏,j=1,執行第一個swap(ch,1,1),這個狀態我稱爲 狀態C1 ,再進入fun函數,此時標記爲T1,i爲2,那麼這時就進入上一個if,將"abc"放進list中 /////////////-------》此時結果集爲["abc"] //2.執行完list.add以後,遇到return,回退到T1處,接下來執行第二個swap(ch,1,1),狀態C1又恢復爲狀態B //恢復完以後,繼續執行for循環,此時j=2,那麼swap(ch,1,2),獲得"acb",這個狀態我稱爲C2,而後執行fun,此時標記爲T2,發現i+1=2,因此也被添加進結果集,此時return回退到T2處往下執行 /////////////-------》此時結果集爲["abc","acb"] //而後執行第二個swap(ch,1,2),狀態C2迴歸狀態B,而後狀態B的for循環退出回到狀態A // a|b|c(狀態A) // | // |swap(0,0) // | // a|b|c(狀態B) // / \ // swap(1,1)/ \swap(1,2) (狀態C1和狀態C2) // / \ // a|b|c a|c|b //3.回到狀態A以後,繼續for循環,j=1,即swap(ch,0,1),即"bac",這個狀態能夠再次叫作狀態A,下面的步驟同上 /////////////-------》此時結果集爲["abc","acb","bac","bca"] // a|b|c(狀態A) // | // |swap(0,1) // | // b|a|c(狀態B) // / \ // swap(1,1)/ \swap(1,2) (狀態C1和狀態C2) // / \ // b|a|c b|c|a //4.再繼續for循環,j=2,即swap(ch,0,2),即"cab",這個狀態能夠再次叫作狀態A,下面的步驟同上 /////////////-------》此時結果集爲["abc","acb","bac","bca","cab","cba"] // a|b|c(狀態A) // | // |swap(0,2) // | // c|b|a(狀態B) // / \ // swap(1,1)/ \swap(1,2) (狀態C1和狀態C2) // / \ // c|b|a c|a|b //5.最後退出for循環,結束。 for(int j=i;j<ch.length;j++){ swap(ch,i,j); fun(ch,list,i+1); swap(ch,i,j); } } } //交換數組的兩個下標的元素 private void swap(char[] str, int i, int j) { if (i != j) { char t = str[i]; str[i] = str[j]; str[j] = t; } } }