劍指Offer(java版):字符串的排列

題目:輸入一個字符串,打印出該字符串中字符的全部排列。java

例如輸入字符串abc,則打印由字符a,b,c所能排列出來的全部字符串:abc,abc,bac,bca,cab,cba面試

咱們求整個字符串的排列,能夠當作兩步:首先求出全部可能出如今第一 個位置的字符,即把第一個字符和後面全部的字符交換。下圖就是分別把第一個字符a和後面的b,c交換的情景。第二步固定第一個字符,求後面全部字符的排 列。這個時候咱們仍把後面的全部字符分紅兩部分:後面字符的第一個字符,以及這個字符以後的全部字符。而後把第一個字符逐一和它後面的字符交換……算法

注:(a)把字符串分爲兩部分,一部分是字符串的第一個字符,另外一部分是第一個字符之後的全部字符。接下來咱們求陰影部分的字符串的排列。(b)拿第一個字符和它後面的字符逐個交換。數組

即咱們以三個字符abc爲例來分析一下求字符串排列的過程。首先咱們固定第一個字符a,求後面兩個字符bc的排列。當兩個字符bc的排列求好以後,我 們把第一個字符a和後面的b交換,獲得bac,接着咱們固定第一個字符b,求後面兩個字符ac的排列。如今是把c放到第一位置的時候了。記住前面咱們已經 把原先的第一個字符a和後面的b作了交換,爲了保證此次c仍然是和原先處在第一位置的a交換,咱們在拿c和第一個字符交換以前,先要把b和a交換回來。在 交換b和a以後,再拿c和處在第一位置的a進行交換,獲得cba。咱們再次固定第一個字符c,求後面兩個字符b、a的排列。app


爲方便起見,用123來示例下。123的全排列有12三、13二、21三、23一、3十二、321這六種。首先考慮213和321這二個數是如何得出的。 顯然這二個都是123中的1與後面兩數交換獲得的。而後能夠將123的第二個數和每三個數交換獲得132。同理能夠根據213和321來得231和 312。所以能夠知道——全排列就是從第一個數字起每一個數分別與它後面的數字交換。函數

 

分析到這裏,咱們就能夠看出,這實際上是很典型的遞歸思路,因而咱們寫出下面的Java代碼:優化

package cglib;ui

 

public class DeleteNode{
    spa

    public static void main(String[] args) {  遞歸

        char buf[]={'a','b','c'};  

        perm(buf,0,buf.length-1);  

    }  

    public static void perm(char[] buf,int start,int end){  

//這個判斷很關鍵,每次遞歸到最後的時候,就是START每次都等於END的時候,就是要打印出相應的全排列字符串的時候,
        if(start==end){//這個判斷用於遞歸到最後的時候輸出相應的字符串  

            for(int i=0;i<=end;i++){  

                System.out.print(buf[i]);  

            }  

            System.out.println();     

        }  

        else{//這個else塊的做用有1:交換第一個位置的字符,好比第一個位置的全排列字符串所有打印後

   //就把第一個字符和第二個交換;2:遞歸打印每次第一個字符串的全排列字符串;3:每次

//遞歸的時候都會傳遞一個字符串數組,最後三行代碼就是控制這個字符串數組不變,意思就是

//什麼樣子傳遞出去,就什麼樣子傳遞回來,一點不能變化,由於最後三行代碼不是用於改變

//字符串數組的

            for(int i=start;i<=end;i++){  
                System.out.println("交換前:buf[start="+start+"]="+buf[start]);
                System.out.println("交換前:buf[i="+i+"]="+buf[i]);
                char temp=buf[start];
                //第一次輸出abc,這三行代碼用於控制第一個位置的字符,就是做用1  

                buf[start]=buf[i]; //好比,第一次時的第一個字符的全排列輸出完後,

//而後把第一個和第二個字符進行交換,交換後,再排列                 
                buf[i]=temp;  //剛被交換到第一個位置字符的 全排列字符串                 
                System.out.println("交換後:buf[start="+start+"]="+buf[start]);
                System.out.println("交換後:buf[i="+i+"]="+buf[i]);
                System.out.println("進入perm,start+1");
                perm(buf,start+1,end);//經過start控制要被輸出的字符串,對應做用2

                temp=buf[start];//這三行是把變換後的字符串順序進行還原,可以變換字符串順序的,對應做用3  

//代碼就在遞歸函數perm()上面三行,用於交換字符串順序來              
                buf[start]=buf[i];  //交換出所需的全排列字符串

                buf[i]=temp;
                System.out.println("變回來:buf[start="+start+"]="+buf[start]);
                System.out.println("變回來:buf[i="+i+"]="+buf[i]);  

            }  

        }  

    }
   

     }


輸出:

交換前:buf[start=0]=a
交換前:buf[i=0]=a
交換後:buf[start=0]=a
交換後:buf[i=0]=a
進入perm,start+1
交換前:buf[start=1]=b
交換前:buf[i=1]=b
交換後:buf[start=1]=b
交換後:buf[i=1]=b
進入perm,start+1
abc
變回來:buf[start=1]=b
變回來:buf[i=1]=b
交換前:buf[start=1]=b
交換前:buf[i=2]=c
交換後:buf[start=1]=c
交換後:buf[i=2]=b
進入perm,start+1
acb
變回來:buf[start=1]=b
變回來:buf[i=2]=c
變回來:buf[start=0]=a
變回來:buf[i=0]=a
交換前:buf[start=0]=a
交換前:buf[i=1]=b
交換後:buf[start=0]=b
交換後:buf[i=1]=a
進入perm,start+1
交換前:buf[start=1]=a
交換前:buf[i=1]=a
交換後:buf[start=1]=a
交換後:buf[i=1]=a
進入perm,start+1
bac
變回來:buf[start=1]=a
變回來:buf[i=1]=a
交換前:buf[start=1]=a
交換前:buf[i=2]=c
交換後:buf[start=1]=c
交換後:buf[i=2]=a
進入perm,start+1
bca
變回來:buf[start=1]=a
變回來:buf[i=2]=c
變回來:buf[start=0]=a
變回來:buf[i=1]=b
交換前:buf[start=0]=a
交換前:buf[i=2]=c
交換後:buf[start=0]=c
交換後:buf[i=2]=a
進入perm,start+1
交換前:buf[start=1]=b
交換前:buf[i=1]=b
交換後:buf[start=1]=b
交換後:buf[i=1]=b
進入perm,start+1
cba
變回來:buf[start=1]=b
變回來:buf[i=1]=b
交換前:buf[start=1]=b
交換前:buf[i=2]=a
交換後:buf[start=1]=a
交換後:buf[i=2]=b
進入perm,start+1
cab
變回來:buf[start=1]=b
變回來:buf[i=2]=a
變回來:buf[start=0]=a
變回來:buf[i=2]=c


拓展1:

若是不是求字符的全部排列,而是求字符的全部組合,應該怎麼辦?仍是輸入三個字符a、b、c,則它們的組合有a、b、c、ab、ac、bc、abc。當交換字符串中兩個字符時,雖然能獲得兩個不一樣的排列,但倒是同一個組合。好比ab和ba是不一樣的排列,但只算一個組合。

解題思路:在求一個字符串中全部字符的組合的時候,針對一個字符,有兩種狀況,假設在長度爲n的字符串中選擇長度爲m的組合字符串,

第一是選擇長度爲n的字符串中的第一個字符,那麼要在其他的長度n-1的字符串中選擇m-1個字符

第二是不選擇長度爲n的字符串中的第一個字符,那麼要在其他的長度n-1的字符串中選擇m個字符

遞歸結束的條件就是,當m爲0,即從字符串中再也不選出字符的時候,這個時候已經找到了m個字符的組合,輸出便可。還有一個條件是,當輸入的字符串是串,天然是不能從中選出任何字符的。

 

package cglib;

import java.util.ArrayList;
import java.util.List;

public class DeleteNode{
    

    public static void main(String ss[]) {  
        perm("123");  
        System.out.println();  
    }  
 
    // 求字符串中全部字符的組合abc>a,b,c,ab,ac,bc,abc  
    public static void perm(String s) {  
        List<String> result = new ArrayList<String>();  
        for (int i = 1; i <= s.length(); i++) {  
            perm(s, i, result);  
        }  
    }  
 
    // 從字符串s中選擇m個字符  
    public static void perm(String s, int m, List<String> result) {  
 
        // 若是m==0,則遞歸結束。輸出當前結果  
        if (m == 0) {  
            for (int i = 0; i < result.size(); i++) {  
                System.out.print(result.get(i));  
            }  
            System.out.println();  
            return;  
        }  
 
        if (s.length() != 0) {  
            // 選擇當前元素  
            result.add(s.charAt(0) + "");  
            perm(s.substring(1, s.length()), m - 1, result);  
            result.remove(result.size() - 1);  
            // 不選當前元素  
            perm(s.substring(1, s.length()), m, result);  
        }  
    }

     }

 

輸出:

1
2
3
12
13
23
123

或者用二進制表示:

n個字符的全部組合個數就是2^n  -1 個。既然咱們能夠知道總數,那麼不妨把這些數字換成二進制碼,就有2^n  -1組二進制碼,細心觀察這些0101的二進制碼,咱們就會發現裏面隱藏着一個規律,那就假如咱們把那組二進制跟咱們的字符串聯想在一塊兒,而後把出現1的 位置的字符連起來,不就是對應其中一種組合狀況嗎?換種說法,那2^n  -1個數所對應的二進制碼,就是咱們要的組合啊。那麼接下來咱們只須要把裏面1的索引位置找出來,把字符串裏面對應索引的字符取出來拼在一塊兒就好了~~ 固然我以爲這裏還有優化的餘地,例如如何更快地找出1所在的位置之類~~
     還有,假如目標字符串是26個字母,這種算法列出組合,比網上的那些遞歸方法快差很少一倍(前提條件是不記錄結果,或者直接輸出結果,由於記錄全部結果須要大量內存,不加內存會爆的........

 

package cglib;


public class DeleteNode{
    

      public static void main(String[] args) {
            String temp = "abc";
            String[] results = getResult(temp);
            for (int i = 0; i < results.length; i++) {  
                System.out.println(results[i]);  
            }  
        }
        private static String[] getResult(String str){
            int start = 1;
            int strSize = str.length();
          
            double max = Math.pow(2, strSize-1);
            double total = Math.pow(2, strSize)-1;
            String[] result = new String[(int)total];
            int step;
            int index;
            StringBuilder sb;
            while (start <= total) {
                sb = new StringBuilder();
                step = 1;
                index = 0;
                while (step <= max) {
                    int temp = step & start;
                    if (temp != 0) {
                        sb.append(str.charAt(index));
                    }
                    step <<= 1;
                    index++;
                }
                result[start -1] = sb.toString();
                start++;
            }
            return result;
        }
     }


輸出:

a
b
ab
c
ac
bc
abc

拓展2:

當輸入一個含有8個數字的數組,判斷有沒有可能把這8個數字分別放到正方體的8個頂點上,使得正方體上三組相對的面上的4個頂點的和都相等。


這裏寫圖片描述

分析

能夠先求出a1-a8這8個數字的全部排列,而後判斷有沒有某一個的排列符合題目設定的條件,即a1+a2+a3+a4 = a5+a6+a7+a8,且a1+a3+a5+a7 = a2+a4+a6+a8,且a1+a2+a5+a6=a3=a4+a7+a8。求8個數字的排列和「面試題28:字符串的排列」中求字符串的排列相似,可 以將求8個數字的排列的問題分解下,將8個數字中的1個輪流固定放在數組的第一個位置,而後求剩下7個數字的排列,再依次遞歸下去。

至關於求出8個數字的全排列,判斷有沒有一個排列符合題目給定的條件,即三組對面上頂點的和相等。

package cglib;

 

public class DeleteNode{
    

    public static void main(String[] args) {  

         int A[] = {1,2,3,1,2,3,2,2};  
         int B[] = {1,2,3,1,8,3,2,2};  
        
         
         if(perm(A,0,A.length-1))  
             System.out.println("Yes\n");  
            else  
                 System.out.println("No\n");  
            if(perm(B,0,B.length-1))  
                 System.out.println("Yes\n");  
            else  
                 System.out.println("No\n");
            
        

    }  

    public static boolean perm(int[] buf,int start,int end){  
        if(buf==null || end!=buf.length-1)  
            return false;
        
        boolean result = false;  

        if(start==end){
             if(buf[0]+buf[1]+buf[2]+buf[3]==buf[4]+buf[5]+buf[6]+buf[7] &&  
                     buf[0]+buf[2]+buf[4]+buf[6]==buf[1]+buf[5]+buf[3]+buf[7] &&  
                             buf[0]+buf[1]+buf[4]+buf[5]==buf[2]+buf[3]+buf[6]+buf[7])  
                        {  
                 int i;  
                 for(i=0;i<=end;i++)  
                     {System.out.print(buf[i]); }
                 System.out.println();  
                 result =  true;  
                        }  
            
        }  

        else{
              
            for(int i=start;i<=end;i++){  
                //System.out.println("交換前:buf[start="+start+"]="+buf[start]);
                //System.out.println("交換前:buf[i="+i+"]="+buf[i]);
                int temp=buf[start];
               
                buf[start]=buf[i];                
                buf[i]=temp;                 
                
                result= perm(buf,start+1,end);
                if(result)  
                    break;  
                temp=buf[start];

       
                buf[start]=buf[i];  

                buf[i]=temp;
                //System.out.println("變回來:buf[start="+start+"]="+buf[start]);
                //System.out.println("變回來:buf[i="+i+"]="+buf[i]);  

            }  

        }
        return result;  

    }
   

     }

輸出:

12323212
Yes

No

相關文章
相關標籤/搜索