题目描述
一条包含字母 A−ZA-ZA−Z 的消息通过以下映射进行了 编码 :
1−>A1 -> A1−>A
2−>B2 -> B2−>B
.........
25−>Y25 -> Y25−>Y
26−>Z26 -> Z26−>Z
然而,在 解码 已编码的消息时,你意识到有许多不同的方式来解码,因为有些编码被包含在其它编码当中("222" 和 "555" 与 "252525")。
例如,"111061110611106" 可以映射为:
"AAJFAAJFAAJF" ,将消息分组为 (1,1,10,6)(1, 1, 10, 6)(1,1,10,6)
"KJFKJFKJF" ,将消息分组为 (11,10,6)(11, 10, 6)(11,10,6)
消息不能分组为 (1,11,06)(1, 11, 06)(1,11,06) ,因为 "060606" 不是一个合法编码(只有 "666" 是合法的)。
注意,可能存在无法解码的字符串。
给你一个只含数字的 非空 字符串 sss ,请计算并返回 解码 方法的 总数 。如果没有合法的方式解码整个字符串,返回 000。
题目数据保证答案肯定是一个 323232 位的整数。
示例 1 :
输入 :s = "12"
输出 :2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2 :
输入 :s = "226"
输出 :3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
示例 3 :
输入 :s = "06"
输出 :0
解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)。
算法原理
创建 dpdpdp 数组,确定状态表示:将 iii 位置当做解码的终点,dpidpidpi 就表示到 iii 位置的解码方案数
确定状态转移方程:考虑和 iii 最近的位置 i−1i-1i−1,到达 iii 位置时,有两种情况。第一种情况,只对 sisisi 进行解码,如果 '000' <=si<=<= si <=<=si<= '999',那么 sisisi 的解码就可以成功,相当于在到 i−1i-1i−1 位置的所有方案后面添加了一个字符,所以方案数 dpi+dpi−1dpi + dpi-1dpi+dpi−1,如果 si==0si == 0si==0,那么解码就会失败,方案数 dpi+0dpi + 0dpi+0。第二种情况,将 si−1si-1si−1 和 sisisi 合起来进行解码,如果合起来解码得到的数字 10<=num<=2610 <= num <= 2610<=num<=26,说明解码是成功的,相当于在到 i−2i-2i−2 位置的所有方案后面添加了一个字符串,所以方案数 dpi+dpi−2dpi + dpi-2dpi+dpi−2,如果合起来解码得到的数字不在上述范围内,说明解码失败,方案数 dpi+0dpi + 0dpi+0

初始化:dp0,dp1dp0, dp1dp0,dp1 算的时候会越界,所以初始化 dp0,dp1dp0, dp1dp0,dp1。dp0dp0dp0 初始化时,如果 s0s0s0 是 '000',说明不能解码,dp0=0dp0 = 0dp0=0,如果不为 '000',能够解码,解码方案为 111 种,dp0=1dp0 = 1dp0=1。dp1dp1dp1 初始化时,如果 s0,s1s0, s1s0,s1 都不为 '000',说明它们单独解码能够成功, dp1+1dp1 + 1dp1+1,如果 s0,s1s0, s1s0,s1 合起来的数字 10<=num<=2610 <= num <= 2610<=num<=26,说明合起来解码也能成功,dp1+1dp1 + 1dp1+1,都不满足说明不能解码,dp1=0dp1 = 0dp1=0
填表顺序:由于 dpidpidpi 依赖于 dpi−1dpi-1dpi−1 和 dpi−2dpi-2dpi−2,所以从左往右填表
返回值:dpn−1dpn-1dpn−1 保存了解码方案总数,返回它即可
代码
cpp
class Solution {
public:
int numDecodings(string s)
{
int n = s.size();
vector<int> dp(n, 0);
dp[0] = (s[0] != '0');
if (n == 1)
{
return dp[0];
}
if (s[0] != '0' && s[1] != '0')
{
dp[1] += 1;
}
int num = (s[0] - '0') * 10 + (s[1] - '0');
if (10 <= num && num <= 26)
{
dp[1] += 1;
}
for (int i = 2;i < n;++i)
{
if (s[i] != '0')
{
dp[i] += dp[i - 1];
}
num = (s[i - 1] - '0') * 10 + (s[i] - '0');
if (10 <= num && num <= 26)
{
dp[i] += dp[i - 2];
}
}
return dp[n - 1];
}
};
优化
可以看到上面初始化 dp1dp1dp1 时候的代码和循环内的代码是类似的,所以可以将它们合在一起,只需要让 dpdpdp 数组多使用一个空间即可。图中的数字均为下标

在多使用了一个空间后,需要注意两点:
- 怎么初始化 dp0dp0dp0 保证计算时后面的值不会出错:以上面的图为例,当 i=2i = 2i=2 时,要计算到 s1s1s1 的方案总数,此时会查看 s1s1s1 是否能解码,能解码就让 dp2+dp1dp2 + dp1dp2+dp1。也会查看 s1,s0s1, s0s1,s0 合起来能不能解码,能解码就让 dp2+dp0dp2 + dp0dp2+dp0,合起来能解码是一种方案,所以 dp0dp0dp0 应该初始化为 111
- 确定映射关系:由于 dpdpdp 数组多使用了一个空间,所以 dpidpidpi 保存的是到 si−1si-1si−1 为止的方案总数
代码
cpp
class Solution {
public:
int numDecodings(string s)
{
int n = s.size();
vector<int> dp(n + 1, 0);
dp[0] = 1;
dp[1] = (s[0] != '0');
for (int i = 2;i <= n;++i)
{
if (s[i - 1] != '0')
{
dp[i] += dp[i - 1];
}
int num = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');
if (10 <= num && num <= 26)
{
dp[i] += dp[i - 2];
}
}
return dp[n];
}
};