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 + ;
vector<long> dp(n + , );
dp[] = ;
if (s[] == '') return ;
dp[] = (s[] == '*') ? : ;
for (int i = ; i <= n; ++i) {
if (s[i - ] == '') {
if (s[i - ] == '' || s[i - ] == '') {
dp[i] += dp[i - ];
} else if (s[i - ] == '*') {
dp[i] += * dp[i - ];
} else {
return ;
}
} else if (s[i - ] >= '' && s[i - ] <= '') {
dp[i] += dp[i - ];
if (s[i - ] == '' || (s[i - ] == '' && s[i - ] <= '')) {
dp[i] += dp[i - ];
} else if (s[i - ] == '*') {
dp[i] += (s[i - ] <= '') ? ( * dp[i - ]) : dp[i - ];
}
} else { // s[i - 1] == '*'
dp[i] += * dp[i - ];
if (s[i - ] == '') dp[i] += * dp[i - ];
else if (s[i - ] == '') dp[i] += * dp[i - ];
else if (s[i - ] == '*') dp[i] += * dp[i - ];
}
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 = , e1 = , e2 = , f0, f1, f2, M = 1e9 + ;
for (char c : s) {
if (c == '*') {
f0 = * e0 + * e1 + * e2;
f1 = e0;
f2 = e0;
} else {
f0 = (c > '') * e0 + e1 + (c <= '') * e2;
f1 = (c == '') * e0;
f2 = (c == '') * e0;
}
e0 = f0 % M;
e1 = f1;
e2 = f2;
}
return e0;
}
};

下面这解法由热心网友edyyy提供,在解法二的基础上去掉了两个变量,节省了行数,很符合博主的极简风格,参见代码如下:

解法三:

class Solution {
public:
int numDecodings(string s) {
long e0 = , e1 = , e2 = , f0 = , M = 1e9 + ;
for (char c : s) {
if (c == '*') {
f0 = * e0 + * e1 + * e2;
e1 = e0;
e2 = e0;
} else {
f0 = (c > '') * e0 + e1 + (c <= '') * e2;
e1 = (c == '') * e0;
e2 = (c == '') * 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 题目讲解汇总(持续更新中...)

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

  1. [LeetCode] 639. Decode Ways II 解码方法 II

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

  2. LeetCode OJ:Decode Ways(解码方法)

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

  3. [LeetCode] Decode Ways 解码方法

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

  4. 【JavaScript】【dp】Leetcode每日一题-解码方法

    [JavaScript]Leetcode每日一题-解码方法 [题目描述] 一条包含字母 A-Z 的消息通过以下映射进行了 编码 : 'A' -> 1 'B' -> 2 ... 'Z' -& ...

  5. [Swift]LeetCode639. 解码方法 2 | Decode Ways II

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

  6. [LeetCode] Decode Ways 解码方法个数、动态规划

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

  7. leetcode 639 Decode Ways II

    首先回顾一下decode ways I 的做法:链接 分情况讨论 if s[i]=='*' 考虑s[i]单独decode,由于s[i]肯定不会为0,因此我们可以放心的dp+=dp1 再考虑s[i-1] ...

  8. [LeetCode] Decode Ways [33]

    题目 A message containing letters from A-Z is being encoded to numbers using the following mapping: 'A ...

  9. LeetCode:Decode Ways 解题报告

    Decode WaysA message containing letters from A-Z is being encoded to numbers using the following map ...

随机推荐

  1. 21.C++- "++"操作符重载、隐式转换之explicit关键字、类的类型转换函数

    ++操作符重载 ++操作符分为前置++和后置++,比如: ++a;  a++; ++操作符可以进行全局函数或成员函数重载 重载前置++操作符不需要参数 重载后置++操作符需要一个int类型的占位参数 ...

  2. oracle帐号scott被锁定如何解锁

       具体操作步骤如下:  C:> sqlplus  请输入用户名:sys  输入口令:sys as sysdba //注意:在口令这里输入 的密码后面必须要跟上 as sysdba 才可以.  ...

  3. 网络工具nslookup的使用

    根据域名查询ip 如下所示: bogon:~ hhh$ nslookup www.baidu.com. Server: 192.168.1.254. #默认的DNS服务器 Address: . #ip ...

  4. Spring-MongoDB 关键类的源码分析

    本文分析的是 spring-data-mongodb-1.9.2.RELEASE.jar 和 mongodb-driver-core-3.2.2.jar. 一.UML Class Diagram 核心 ...

  5. 第2次作业:软件分析之Steam

    1. 作业内容 1.1 介绍产品相关信息 你选择的产品是? 我选择的分析的软件为STEAM 为什么选择该产品作为分析? 在上述列表中的产品,除了王者荣耀,其他几项都是平时我使用较为频繁的软件,所以最初 ...

  6. Alpha冲刺第十一天

    Alpha冲刺第十一天 站立式会议 项目进展 项目进入尾声,主要测设工作完成过半,项目总结也开始进行. 问题困难 项目的困难现阶段主要是测试过程中存在一些"盲点"很难发现或者发现后 ...

  7. SQLAlchemy 教程 —— 基础入门篇

    SQLAlchemy 教程 -- 基础入门篇 一.课程简介 1.1 实验内容 本课程带领大家使用 SQLAlchemy 连接 MySQL 数据库,创建一个博客应用所需要的数据表,并介绍了使用 SQLA ...

  8. RxSwift:ReactiveX for Swift 翻译

    RxSwift:ReactiveX for Swift 翻译 字数1787 阅读269 评论3 喜欢3 图片发自简书App RxSwift | |-LICENSE.md |-README.md |-R ...

  9. C简单实现双向链表

    <pre name="code" class="cpp">//链表结构 typedef struct DulNode { DataType data ...

  10. datetimepicker.js 使用笔记

    1.官网地址 官网传送门 2.属性及使用示例 2.1调用 html: <input  type="text"  readonly class="date" ...