[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;
    }
};

参考资料:

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

 本文转自博客园Grandyang的博客,原文链接:[LeetCode] Decode Ways II 解码方法之二

,如需转载请自行联系原博主。

时间: 2024-09-28 12:43:04

[LeetCode] Decode Ways II 解码方法之二的相关文章

[LeetCode] Bulb Switcher II 灯泡开关之二

There is a room with n lights which are turned on initially and 4 buttons on the wall. After performing exactly m unknown operations towards buttons, you need to return how many different kinds of status of the n lights could be. Suppose n lights are

[LeetCode] Beautiful Arrangement II 优美排列之二

Given two integers n and k, you need to construct a list which contains n different positive integers ranging from 1 to n and obeys the following requirement:  Suppose this list is [a1, a2, a3, ... , an], then the list [|a1 - a2|, |a2 - a3|, |a3 - a4

[LeetCode] My Calendar II 我的日历之二

Implement a MyCalendarTwo class to store your events. A new event can be added if adding the event will not cause a triple booking. Your class will have one method, book(int start, int end). Formally, this represents a booking on the half open interv

[LeetCode]91.Decode Ways

题目 A message containing letters from A-Z is being encoded to numbers using the following mapping: 'A' -> 1 'B' -> 2 - 'Z' -> 26 Given an encoded message containing digits, determine the total number of ways to decode it. For example, Given encode

[LeetCode] Sentence Similarity II 句子相似度之二

Given two sentences words1, words2 (each represented as an array of strings), and a list of similar word pairs pairs, determine if two sentences are similar. For example, words1 = ["great", "acting", "skills"] and words2 = [&

[LeetCode] Valid Palindrome II 验证回文字符串之二

Given a non-empty string s, you may delete at most one character. Judge whether you can make it a palindrome. Example 1: Input: "aba" Output: True Example 2: Input: "abca" Output: True Explanation: You could delete the character 'c'. N

Decode Ways

A message containing letters from A-Z is being encoded to numbers using the following mapping: 'A' -> 1 'B' -> 2 ... 'Z' -> 26 Given an encoded message containing digits, determine the total number of ways to decode it. For example,Given encoded

Docker容器互联方法--篇二

本文讲的是Docker容器互联方法--篇二,[编者的话]本文为Eddy Mavungu博士于DEIS官方博客中发布的系列文章的第二部分,Eddy博士在本篇系列文章中分享了Docker容器间互联的方法,并且做了演示.Eddy博士是DEIS公司的创始人,同时也是一位高级研究顾问.本文根据他于DEIS官方博客上发布的文章翻译而成. 这篇系列文章的第二部分会看一下如何连接Docker各容器. 我们在第一节谈及了Docker的bridge接口,它可以让我们连接所有在相同Docker宿主机上的容器.特别需要

JavaScript 特有方法计算二进制中1的个数 split方法_javascript技巧

代码如下: 复制代码 代码如下: function g(n){ var n = n.toString(2); var count = 0; for(var i=0;i<n.length;i++) { if(n[i] == "1") count++; } return count; } 觉得这样写很麻烦,突然想到是不是可以利用js的split方法来实现计算1的个数,split的参数为正则\0*\,分离字符串中的1.代码如下: 复制代码 代码如下: function f(n){ re