[LeetCode] Decode Ways II 解碼方法之二


 

A message containing letters from A-Z is being encoded to numbers using the following mapping way:

'A' -> 1
'B' -> 2
...
'Z' -> 26

Beyond that, now the encoded string can also contain the character '*', which can be treated as one of the numbers from 1 to 9.

Given the encoded message containing digits and the character '*', return the total number of ways to decode it.

Also, since the answer may be very large, you should return the output mod 109 + 7.

Example 1:

Input: "*"
Output: 9
Explanation: The encoded message can be decoded to the string: "A", "B", "C", "D", "E", "F", "G", "H", "I".

 

Example 2:

Input: "1*"
Output: 9 + 9 = 18

 

Note:

  1. The length of the input string will fit in range [1, 105].
  2. The input string will only contain the character '*' and digits '0' - '9'.

 

這道解碼的題是之前那道Decode Ways的拓展,難度提高了不少,引入了星號,可以代表1到9之間的任意數字,是不是有點外卡匹配的感覺。有了星號以后,整個題就變得異常的復雜,所以結果才讓我們對一個很大的數求余,避免溢出。這道題的難點就是要分情況種類太多,一定要全部理通順才行。我們還是用DP來做,建立一個一維dp數組,其中dp[i]表示前i個字符的解碼方法等個數,長度為字符串的長度加1。將dp[0]初始化為1,然后我們判斷,如果字符串第一個字符是0,那么直接返回0,如果是*,則dp[1]初始化為9,否則初始化為1。下面就來計算一般情況下的dp[i]了,我們從i=2開始遍歷,由於要分的情況種類太多,我們先選一個大分支,就是當前遍歷到的字符s[i-1],只有三種情況,要么是0,要么是1到9的數字,要么是星號。我們一個一個來分析:

首先來看s[i-1]為0的情況,這種情況相對來說比較簡單,因為0不能單獨拆開,只能跟前面的數字一起,而且前面的數字只能是1或2,其他的直接返回0即可。那么當前面的數字是1或2的時候,dp[i]的種類數就跟dp[i-2]相等,可以參見之前那道Decode Ways的講解,因為后兩數無法單獨拆分開,就無法產生新的解碼方法,所以只保持住原來的拆分數量就不錯了;如果前面的數是星號的時候,那么前面的數可以為1或者2,這樣就相等於兩倍的dp[i-2];如果前面的數也為0,直接返回0即可。

再來看s[i-1]為1到9之間的數字的情況,首先搞清楚當前數字是可以單獨拆分出來的,那么dp[i]至少是等於dp[i-1]的,不會拖后腿,還要看其能不能和前面的數字組成兩位數進一步增加解碼方法。那么就要分情況討論前面一個數字的種類,如果當前數字可以跟前面的數字組成一個小於等於26的兩位數的話,dp[i]還需要加上dp[i-2];如果前面的數字為星號的話,那么要看當前的數字是否小於等於6,如果是小於等於6,那么前面的數字就可以是1或者2了,此時dp[i]需要加上兩倍的dp[i-2],如果大於6,那么前面的數字只能是1,所以dp[i]只能加上dp[i-2]。

最后來看s[i-1]為星號的情況,如果當前數字為星號,那么就創造9種可以單獨拆分的方法,所以那么dp[i]至少是等於9倍的dp[i-1],還要看其能不能和前面的數字組成兩位數進一步增加解碼方法。那么就要分情況討論前面一個數字的種類,如果前面的數字是1,那么當前的9種情況都可以跟前面的數字組成兩位數,所以dp[i]需要加上9倍的dp[i-2];如果前面的數字是2,那么只有小於等於6的6種情況都可以跟前面的數字組成兩位數,所以dp[i]需要加上6倍的dp[i-2];如果前面的數字是星號,那么就是上面兩種情況的總和,dp[i]需要加上15倍的dp[i-2]。

每次算完dp[i]別忘了對超大數取余,參見代碼如下:

 

解法一:

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size(), M = 1e9 + 7;
        vector<long> dp(n + 1, 0);
        dp[0] = 1;
        if (s[0] == '0') return 0;
        dp[1] = (s[0] == '*') ? 9 : 1;
        for (int i = 2; i <= n; ++i) {
            if (s[i - 1] == '0') {
                if (s[i - 2] == '1' || s[i - 2] == '2') {
                    dp[i] += dp[i - 2];
                } else if (s[i - 2] == '*') {
                    dp[i] += 2 * dp[i - 2];
                } else {
                    return 0;
                }
            } else if (s[i - 1] >= '1' && s[i - 1] <= '9') {
                dp[i] += dp[i - 1];
                if (s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6')) {
                    dp[i] += dp[i - 2];
                } else if (s[i - 2] == '*') {
                    dp[i] += (s[i - 1] <= '6') ? (2 * dp[i - 2]) : dp[i - 2];
                }
            } else { // s[i - 1] == '*'
                dp[i] += 9 * dp[i - 1];
                if (s[i - 2] == '1') dp[i] += 9 * dp[i - 2];
                else if (s[i - 2] == '2') dp[i] += 6 * dp[i - 2];
                else if (s[i - 2] == '*') dp[i] += 15 * dp[i - 2];
            }
            dp[i] %= M;
        }
        return dp[n];
    }
};

 

下面這種解法是論壇上排名最高的解法,常數級的空間復雜度,寫法非常簡潔,思路也巨牛逼,博主是無論如何也想不出來的,只能繼續當搬運工了。這里定義了一系列的變量e0, e1, e2, f0, f1, f2。其中:

e0表示當前可以獲得的解碼的次數,當前數字可以為任意數 (也就是上面解法中的dp[i])

e1表示當前可以獲得的解碼的次數,當前數字為1

e2表示當前可以獲得的解碼的次數,當前數字為2

f0, f1, f2分別為處理完當前字符c的e0, e1, e2的值

那么下面我們來進行分類討論,當c為星號的時候,f0的值就是9*e0 + 9*e1 + 6*e2,這個應該不難理解了,可以參考上面解法中的講解,這里的e0就相當於dp[i-1],e1和e2相當於兩種不同情況的dp[i-2],此時f1和f2都賦值為e0,因為要和后面的數字組成兩位數的話,不會增加新的解碼方法,所以解碼總數跟之前的一樣,為e0, 即dp[i-1]。

當c不為星號的時候,如果c不為0,則f0首先應該加上e0。然后不管c為何值,e1都需要加上,總能和前面的1組成兩位數;如果c小於等於6,可以和前面的2組成兩位數,可以加上e2。然后我們更新f1和f2,如果c為1,則f1為e0;如果c為2,則f2為e0。

最后別忘了將f0,f1,f2賦值給e0,e1,e2,其中f0需要對超大數取余,參見代碼如下:

 

解法二:

class Solution {
public:
    int numDecodings(string s) {
        long e0 = 1, e1 = 0, e2 = 0, f0, f1, f2, M = 1e9 + 7;
        for (char c : s) {
            if (c == '*') {
                f0 = 9 * e0 + 9 * e1 + 6 * e2;
                f1 = e0;
                f2 = e0;
            } else {
                f0 = (c > '0') * e0 + e1 + (c <= '6') * e2;
                f1 = (c == '1') * e0;
                f2 = (c == '2') * e0;
            }
            e0 = f0 % M;
            e1 = f1;
            e2 = f2;
        }
        return e0;
    }
};

 

下面這解法由熱心網友edyyy提供,在解法二的基礎上去掉了兩個變量,節省了行數,很符合博主的極簡風格,參見代碼如下:

 

解法三:

class Solution {
public:
    int numDecodings(string s) {
        long e0 = 1, e1 = 0, e2 = 0, f0 = 0, M = 1e9 + 7;
        for (char c : s) {
            if (c == '*') {
                f0 = 9 * e0 + 9 * e1 + 6 * e2;
                e1 = e0;
                e2 = e0;
            } else {
                f0 = (c > '0') * e0 + e1 + (c <= '6') * e2;
                e1 = (c == '1') * e0;
                e2 = (c == '2') * e0;
            }
            e0 = f0 % M;
        }
        return e0;
    }
};

 

類似題目:

Decode Ways

 

參考資料:

https://discuss.leetcode.com/topic/95301/python-straightforward-with-explanation

https://discuss.leetcode.com/topic/95518/java-o-n-by-general-solution-for-all-dp-problems

https://discuss.leetcode.com/topic/95204/java-dp-solution-o-n-time-and-space-some-explanations

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM