劍指Offer[38]:字符串的排列

題目

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

題目來源於牛客網,對劍指Offer書中字符串排列問題做了小小修改,也就是需要按字典序打印出所有的排列。也就是說兩個字符排列需要根據從左到右逐個比較對應的字符的先後來確定先後順序。

1. 常規解法

劍指Offer中給出的解法最後得到的所有全排列是亂序的,因此最後再對得到的所有字符串進行排序就行了。核心的思路就是以下兩步:

  1. 求所有出現在第一個位置的字符,就是把第一個位置的字符和後面所有位置的字符交換;
  2. 固定第一個位置的字符,求後面所有字符的全排列。

以"abc"爲例,求它的全排列可以用下圖描述(圖來源於百度):

從圖和上面的步驟2可以看到這顯然是一種遞歸的思路,最後代碼如下:

import java.util.ArrayList;
import java.util.Collections;
public class Solution38 {
    public ArrayList<String> Permutation(String str) {
        if(str.length() <= 0){
            return new ArrayList();
        }
        ArrayList<String> strList = new ArrayList();
        Permutation(str.toCharArray(), strList, 0);  
        Collections.sort(strList);  // 牛客網的題目要求所有排列按字典順序打印
        return (ArrayList)strList;
    }
    
    private void Permutation(char[] strArr, ArrayList strList, int begin){
        if(begin == strArr.length - 1){   // 已經到了字符串末尾並且列表中沒有當前字符串
            if(!strList.contains(new String(strArr))){   // 將其加入列表中
                strList.add(new String(strArr));
            }
            return;
        }
        for(int i = begin; i < strArr.length; i++){  // 依次交換頭部字符和後面的每一個字符
                swap(strArr, i, begin);   
                Permutation(strArr, strList, begin + 1);  // 遞歸地對後面部分進行排列
                swap(strArr, i, begin);    // 復位操作,相當於回溯
            }
    }
    
    private void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

最後在得到所有的全排列之後,用Collections類的sort函數對存儲全排列的List進行排序就可以得到字典序的全排列。

2. 非遞歸的方法

偶然看到的一個大佬的解法,原文在這:https://www.cnblogs.com/pmars/archive/2013/12/04/3458289.html
一個全排列可看做一個字符串,字符串可有前綴、後綴。生成給定全排列的下一個排列.所謂一個的下一個就是這一個與下一個之間沒有其他的。這就要求這一個與下一個有儘可能長的共同前綴,也即變化限制在儘可能短的後綴上。
直接看這種方法的思路吧:

設P是[1,n]的一個全排列。P=P1P2Pj1PjPj+1Pk1PkPk+1PnP_1P_2…P_{j-1}P_jP_{j+1}…P_{k-1}P_kP_{k+1}…P_n
Find: j=max{iPi<Pi+1}j = max\{i|P_i<P_i+1\}
   k=max{iPi>Pj}k = max\{i|P_i>P_j\}
   swap(Pi, Pj)
   反轉Pj+1P_{j+1}以後的部分(包括Pj+1P_{j+1}
   得到的P’就是P按字典序的下一個全排列

【例】 如何得到346987521的下一個全排列

1,從尾部往前找第一個P(i-1) < P(i)的位置
     4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
   最終找到6是從後向前第一個變小的數字,記錄下6的位置i-1
2,從i位置往後找到最後一個大於6的數
     4 6 -> 9 -> 8 -> 7 5 2 1
   最終找到7的位置,記錄位置爲m
3,交換位置i-1和m的值
     4 7 9 8 6 5 2 1
4,倒序i位置後的所有數據
     4 7 1 2 5 6 8 9
則347125689爲346987521的下一個排列

這種解法的代碼如下,最後得到的結果就是按字典序排序的,不需要再排序:

import java.util.ArrayList;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        if(str.length() <= 0){
            return new ArrayList();
        }
        ArrayList<String> strList = new ArrayList();
        char[] chars = str.toCharArray();
        strList.add(String.valueOf(chars));
        int length = chars.length;
        int left, right;
        while(true){
            left = length - 1;
            // 向前查找第一個變小的元素
            while(left > 0 && chars[left] <= chars[left - 1]){
                left--;
            }
            if(left == 0){
                break;
            }
            right = left;
            // 向後查找最後一個大於chars[left - 1]的元素
            while(right + 1 < length && chars[right + 1] > chars[left - 1]){
                right++;
            }
            // 交換兩個值
            swap(chars, left - 1, right);
            reverse(chars, left);  // 對left以後的部分進行反轉
            strList.add(String.valueOf(chars));
        }
        return strList;
    }
    private void reverse(char[] chars, int start){
        if(start >= chars.length - 1){
            return;
        }
        int length = chars.length;
        for(int i = start; i < (length + start) / 2; i++){
            swap(chars, i, length + start - i - 1);
        }
    }
    
    private void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
發佈了34 篇原創文章 · 獲贊 3 · 訪問量 3357
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章