Java輕鬆搞定leetcode前100系列之38. 外觀數列

求學旅途發表於2020-12-31

題目描述:

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

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

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

countAndSay(1) = "1"
countAndSay(n) 是對 countAndSay(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"

要 描述 一個數字字串,首先要將字串分割為 最小 數量的組,每個組都由連續的最多 相同字元 組成。然後對於每個組,先描述字元的數量,然後描述字元,形成一個描述組。要將描述轉換為數字字串,先將每組中的字元數量用數字替換,再將所有描述組連線起來。

例如,數字字串 "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(1<=n<=30),要求輸出外觀數列第 n 項。

【外觀數列】:是一個整數序列,數字由 1 開始,序列中每一項都是對前一項的描述。

例如:


1.     1
2.     11

這裡例子給出兩項,其中:

第一項由 1 開始;

第二項是對第一項的描述,這裡第一項的 1,被描述為 1 個 1,記為 11。

序列後續以此類推,都是後面對前面項的描述。其中整數序列的每一項都以字串的形式表示。

這裡,我們可以發現,除第一項外,每一項都是對前面項的描述。也就是說,只要知道前面項,那麼就能有辦法求得當前項。

現在,假設題目給定正整數 n,要求外觀數列的第 n 項。那麼這裡需要得知第 n-1 項的內容,而第 n-1 項則須知第 n-2 項,...,以此類推。而在這道題中,第一項就是 1,那麼就可以由此逐項往下遞推。

這裡主要的問題是如何去描述前一項得到當前項?

看題目中的示例,例如第 5 項,111221。現在要描述此項獲得第 6 項,現在我們直觀來看,有 3 個 1,2 個 2,1 個 1,那麼記為 312211。這就是最直觀能夠得出的結論,那我們來看看,就這個例子具體的思路:

從左往右觀察第 5 項,111221,其中 1112 這部分中,在遇到 2 之前,前面的 1 出現了 3 次,那麼此時記下出現的次數以及具體的元素,進而將其拼接。也就是 31;
同樣的,緊接部分 221,這裡,在遇到後面的 1 之前,前面的 2 出現了 2 次,這裡拼接,記為 22;
最後部分的 1,後續沒有相同的元素,也就僅出現一次,記為 11。
根據上面的思路,當給定任意的正整數 n(1<=n<=30)時,我們可以通過下面雙指標的做法,去逐項描述,求得第 n 項:

首先定義變數 pre 記錄前一項,初始化為空字串;定義變數 cur 記錄當前項,初始化為 '1'(第一項為 1);
定義雙指標 start, end 均指向序列項的頭部,這裡用於統計元素出現的次數;
由於給定的n≥1,這裡由第 2 項開始逐項對前一項進行描述(注意,要將 cur 賦值給 pre,並初始化 cur 為空字串,重新拼接得到當前項):
從左往右遍歷 pre,當元素相同時,移動 end 指標,直至元素不相同時,那麼此時 end-start 就是相同元素的個數,而 start 指標指向的元素就是重複的元素,進行拼接,cur += str(end-start)+pre[start]。
此時要讓 start 指向 end 所在的位置,開始記錄下個元素出現的次數;
重複上面的步驟,直至 end 指標到達序列項尾部,便可得到當前項。
逐項對前面一項描述開始時,都應該重置 start、end 指標指向序列項頭部,同時應將 cur 賦值給 pre,初始化 cur,也就是前面注意部分所說的內容(可結合程式碼理解)。然後,再次重複第三個步驟。

程式碼如下:

    public String countAndSay(int n) {
        StringBuilder pre = new StringBuilder("1");
        StringBuilder cur = new StringBuilder("1");
        for (int i = 1; i < n; i++) {
            pre = cur;
            cur = new StringBuilder();
            int start=0,end=0;
            while (end<pre.length()){
                while (end<pre.length()&&pre.charAt(start)==pre.charAt(end)){
                    end++;
                }
                cur=cur.append(end-start).append(pre.charAt(start));
                start=end;
            }
        }
        return cur.toString();
    }

 

相關文章