一、题目
一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
‘A’ -> 1
‘B’ -> 2
…
‘Z’ -> 26
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:
“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。
给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。
题目数据保证答案肯定是一个 32 位 的整数。
点击查看原题
二、思路
对于编码的映射方式计数,很像一道题:爬楼梯的方式,也是一道可以化简为最优子问题的dp题。
映射有两种可能:
- 单个字符做映射,即字符不为0字符
- 两个字符做映射,即第一个字符为1(10-19),或者第一个字符为2且第二个字符小于等于6(20-26)
对于字符串s,s[0,n-1]的解码方法总数依赖于n-2处是否可以和n-1处字符组合来做映射,也就是说,可以通过这种方式求最优解:
d
p
[
i
]
+
=
{
d
p
[
i
−
1
]
,
′
0
′
<
s
[
i
]
≤
′
9
′
d
p
[
i
−
2
]
,
s
[
i
−
1
]
=
′
1
′
∣
∣
(
s
[
i
−
1
]
=
′
2
′
&
&
s
[
i
]
≤
′
6
′
)
dp[i]+=\left\{ \begin{aligned} dp[i-1]&,& '0'<s[i]\leq'9'\\ dp[i-2]&,&s[i-1]='1'||(s[i-1]='2'\&\&s[i]\leq'6') \\ \end{aligned} \right.
dp[i]+={dp[i−1]dp[i−2],,′0′<s[i]≤′9′s[i−1]=′1′∣∣(s[i−1]=′2′&&s[i]≤′6′)
请注意,上式不是简单的等号,而是+=,dp[i]的初始值是0,这样就充分考虑了无法构成映射的情况。
三、代码
class Solution {
public int numDecodings(String s) {
int[] dp = new int[s.length()];
char[] cs = s.toCharArray();
dp[0] = cs[0] == '0' ? 0 : 1; // 初始化第一位
for (int i = 1; i < s.length(); i++) {
int val = 0;
if (cs[i] != '0') { // 单个字符做映射,即字符不为0字符
val += dp[i-1];
}
// 两个字符做映射
// i-1处字符为1(10-19),或者i-1处字符为2且i处字符小于等于6(20-26)
if (cs[i-1] == '1' || (cs[i-1] == '2' && cs[i] <= '6')) {
val += (i-2) < 0 ? 1 : dp[i-2];
}
dp[i] = val;
}
return dp[dp.length-1];
}
}
由于i项的解,只依赖于i-1和i-2的状态,可以优化为常数空间。
class Solution {
public int numDecodings(String s) {
char[] cs = s.toCharArray();
// i-2状态
int first = 1; // 当i=1时,如果需要访问dp[i-2]的状态,应该为1
// i-1状态
int second = cs[0] == '0' ? 0 : 1;
for (int i = 1; i < s.length(); i++) {
int val = 0;
if (cs[i] != '0') { // 单个字符做映射,即字符不为0字符
val += second;
}
// 两个字符做映射
// i-1处字符为1(10-19),或者i-1处字符为2且i处字符小于等于6(20-26)
if (cs[i-1] == '1' || (cs[i-1] == '2' && cs[i] <= '6')) {
val += first;
}
first = second;
second = val;
}
return second;
}
}