1. 程式人生 > 實用技巧 >LeetCode初級演算法之字串:38 外觀數列

LeetCode初級演算法之字串:38 外觀數列

外觀數列

題目地址:https://leetcode-cn.com/problems/count-and-say/

給定一個正整數 n ,輸出外觀數列的第 n 項。

「外觀數列」是一個整數序列,從數字 1 開始,序列中的每一項都是對前一項的描述。

你可以將其視作是由遞迴公式定義的數字字串序列:

countAndSay(1) = "1"
countAndSay(n) 是對 countAndSay(n-1) 的描述,然後轉換成另一個數字字串。

//前五項如下:

1
11
21
1211
111221

第一項是數字 1
描述前一項,這個數是 1 即 “ 一 個 1 ”,記作 "11"
描述前一項,這個數是 11 即 “ 二 個 1 ” ,記作 "21"
描述前一項,這個數是 21 即 “ 一 個 2 + 一 個 1 ” ,記作 "1211"
描述前一項,這個數是 1211 即 “ 一 個 1 + 一 個 2 + 二 個 1 ” ,記作 "111221"
要 描述 一個數字字串,首先要將字串分割為 最小 數量的組,每個組都由連續的最多 相同字元 組成。然後對於每個組,先描述字元的數量,然後描述字元,形成一個描述組。要將描述轉換為數字字串,先將每組中的字元數量用數字替換,再將所有描述組連線起來。

例如,數字字串 "3322251" 的描述如下圖:

示例 1:

輸入:n = 1
輸出:"1"
解釋:這是一個基本樣例。

示例 2:

輸入:n = 4
輸出:"1211"
解釋:
countAndSay(1) = "1"
countAndSay(2) = 讀 "1" = 一 個 1 = "11"
countAndSay(3) = 讀 "11" = 二 個 1 = "21"
countAndSay(4) = 讀 "21" = 一 個 2 + 一 個 1 = "12" + "11" = "1211"

提示:

1 <= n <= 30

解法一:暴力解法

很直觀的我們要去掃描一個串並且按照順序統計連續相同的數字數量,出現不同則中斷重新統計。最後得到一個新串這是一次迭代。按照上面的例子要進行n次迭代,每次用上一次得到的串。

public String countAndSay(int n){
    //上代串
    String str = "1";
    //本次迭代串
    StringBuilder result = new StringBuilder();
    //迭代n次
    for(int c = 1; c < n; c++){
        //1. 清空迭代串
        result.delete(0,result.length());
        //2. 雙指標掃描
        int i = 0, j = 0;
        while(i < str.length() && j < str.length()){
            if(str.charAt(i) != str.charAt(j)){
                //拼接上相同數量和對應數字
                result.append(j - i).append(str.charAt(i));
                i = j;
            }
            if(j == str.length() - 1){
                result.append(j - i + 1).append(str.charAt(i));
            }
            j++;    
        }
        str = result.toString();
    }
    return str;
}

解法二:遞迴(解一)

換個花樣的話,也可以把上面迭代n次該為遞迴呼叫n次的方式

public String countAndSay(int n) {
    // 遞迴終止條件
    if (n == 1) {
        return "1";
    }
    // 本次迭代串
    StringBuilder result = new StringBuilder();
    // 上代串
    String str = countAndSay(n - 1);
    // 雙指標掃描
    int i = 0, j = 0;
    while(i < str.length() && j < str.length()){
        if(str.charAt(i) != str.charAt(j)){
            result.append(j - i).append(str.charAt(i));
            i = j;
        }
        if(j == str.length() - 1){
            result.append(j - i + 1).append(str.charAt(i));
        }
        j++;    
    }
    return res.toString();
}

解法三 :正則表示式

玩的再花一點可以用下正則表示式。去匹配相同的數字為一組,用到java.util.regex下的Pattern與Matcher,

"."是表示任意字元,
"//1"引用前面括號表示的內容
"*" 0個或多個
"(.)\\1*"第一個字元是任意的第二個要麼是前一個字元要麼就沒有,*後面都和第二位一樣
字串"aaabc"過濾出來就是aaa、b、c

程式碼:

public String countAndSay(int n) {
    String str = "1";
    Pattern pattern = Pattern.compile("(.)\\1*");
    for (int i = 1; i < n; i++) {
        Matcher m = pattern.matcher(str);
        StringBuilder result = new StringBuilder();
        while (m.find()) {
            result.append(m.group().length() + String.valueOf(m.group().charAt(0)));
        }
        str = result.toString();
    }
    return str;
}

總結

對於這題的話還是比較好做的,因為在直接解法裡面並沒有多餘的步驟本身就是優的。使用解法三目的只是熟悉一下Java類庫關於正則的一些操作和正則表示式,本身對題來說裡面很多操作是多餘的因此效率是低的。解一解二在複雜度相同的情況下,解二雖然在這上面顯示的好像快一點但遞迴迴圈呼叫方法建立棧幀進棧出棧還是有消耗的,較優解還是最開始的解一