1. 程式人生 > 實用技巧 >每日一道 LeetCode (11):外觀數列

每日一道 LeetCode (11):外觀數列

每天 3 分鐘,走上演算法的逆襲之路。

前文合集

每日一道 LeetCode 前文合集

程式碼倉庫

GitHub: https://github.com/meteor1993/LeetCode

Gitee: https://gitee.com/inwsy/LeetCode

題目:外觀數列

題目來源:https://leetcode-cn.com/problems/count-and-say/

給定一個正整數 n(1 ≤ n ≤ 30),輸出外觀數列的第 n 項。

注意:整數序列中的每一項將表示為一個字串。

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

1.     1
2.     11
3.     21
4.     1211
5.     111221

第一項是數字 1

描述前一項,這個數是 1 即 “一個 1 ”,記作 11

描述前一項,這個數是 11 即 “兩個 1 ” ,記作 21

描述前一項,這個數是 21 即 “一個 2 一個 1 ” ,記作 1211

描述前一項,這個數是 1211 即 “一個 1 一個 2 兩個 1 ” ,記作 111221

示例 1:

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

示例 2:

輸入: 4
輸出: "1211"
解釋:當 n = 3 時,序列是 "21",其中我們有 "2" 和 "1" 兩組,"2" 可以讀作 "12",也就是出現頻次 = 1 而 值 = 2;類似 "1" 可以讀作 "11"。所以答案是 "12" 和 "11" 組合在一起,也就是 "1211"。

解題思路

今天下班回來太晚了,我就簡單寫寫,有不理解的再私信問我吧。

首先幫忙理解下這道題,這道題的意思實際上就是按照一種規律來進行描述數字。

按照正常人的思路,這個演算法首先需要一個大迴圈,直接迴圈到 n ,然後在每一次迴圈中得到上一次結果的新的描述。

  1. 最開始第一次是 1 , 這個是規定。
  2. 第二次迴圈是描述上一個數字,結果是 11 ,意思是上一個數字是 1 個 1 。
  3. 第三次迴圈上描述上一次的 11 ,結果是 21 ,含義是 2 個 1 。
  4. 第四次迴圈還是描述上一次的 21 ,結果是 1211 ,含義是 1 個 2 , 1 個 1 。
  5. ......

後面的我就不寫了,這道題就先拆解到這裡,確實蠻難理解的,說實話,我看題是沒看懂的,直到我看到了答案分析了程式碼後才看懂題。

我這是真的菜的一批。

解題

首先宣告,這道題我沒有想出來任何解法,所有解法均來自於 LeetCode 的網友。

最皮的解法

第一個我一定要介紹這個解法,因為這個題給出的數字是有限的,所以有一個非常皮的網友直接給了這麼一串程式碼,看的我是目瞪口呆:

public String countAndSay(int n) {
    switch (n) {
        case 1:
            return "1";
        case 2:
            return "11";
        case 3:
            return "21";
        case 4:
            return "1211";
        case 5:
            return "111221";
        case 6:
            return "312211";
        case 7:
        // 中簡程式碼省略,太長了
        ...
        default:
            return "0";
    }
}

這位同學抓住了這道題的輸入是有限制的,直接把所有的結果窮舉出來,然後用了一波 switch ,我是真的服。

果然刷 LeetCode 題的人的腦回路和正常人完全不同。

常規迭代解法

對於題還沒理解清楚的同學可以多 debug 幾次下面這段程式碼,這種方案是一個人的常規正向的思維過程。

public static String countAndSay(int n) {
    if ( n == 1 ) return "1";
    String s_n_1 = "1";
    // 首先外層先迴圈到 n ,從 2 開始。
    for ( int i = 2; i <= n; i++ ) {
        StringBuilder sb = new StringBuilder();
        int pre = 0;// 前驅節點
        int j = 0;
        // 開始描述上一次得到的數字
        for ( j = 0; j < s_n_1.length(); j++ ) {
            if ( s_n_1.charAt(j)!=s_n_1.charAt(pre) ){
                sb.append(j-pre).append(s_n_1.charAt(pre));
                pre = j;
            }
        }
        sb.append(j-pre).append(s_n_1.charAt(pre));
        s_n_1 = sb.toString();
    }
    return s_n_1;
}

不好寫的遞迴方案

遞迴講道理我還是不大會寫,硬著頭皮先抄一遍程式碼吧。

public String countAndSay_1(int n) {
    if (n == 1) return "1";

    StringBuilder res = new StringBuilder();
    String str = countAndSay_1(n - 1);
    int length = str.length();
    int a = 0;

    for (int i = 1; i < length + 1; i++) {
        if (i == length) {
            return res.append(i - a).append(str.charAt(a)).toString();
        } else if (str.charAt(i) != str.charAt(a) ) {
            res.append(i - a).append(str.charAt(a));
            a = i;
        }
    }
    return res.toString();
}

先這樣,今天晚上時間比較緊,寫的內容不多,還望各位看到這篇文章的同學海涵,明天我一定找補回來。