一、题目

一条包含字母 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题。
映射有两种可能:
1)单个字符做映射,即字符不为0字符
2)两个字符做映射,即第一个字符为1(10-19),或者第一个字符为2且第二个字符小于等于6(20-26)
对于字符串s,s[0,n-1]的解码方法总数依赖于n-2处是否可以和n-1处字符组合来做映射,也就是说,可以通过这种方式求最优解:
91. 解码方法-每日一题 - 图1

请注意,上式不是简单的等号,而是+=,dp[i]的初始值是0,这样就充分考虑了无法构成映射的情况。

三、代码

  1. class Solution {
  2. public int numDecodings(String s) {
  3. int[] dp = new int[s.length()];
  4. char[] cs = s.toCharArray();
  5. dp[0] = cs[0] == '0' ? 0 : 1; // 初始化第一位
  6. for (int i = 1; i < s.length(); i++) {
  7. int val = 0;
  8. if (cs[i] != '0') { // 单个字符做映射,即字符不为0字符
  9. val += dp[i-1];
  10. }
  11. // 两个字符做映射
  12. // i-1处字符为1(10-19),或者i-1处字符为2且i处字符小于等于6(20-26)
  13. if (cs[i-1] == '1' || (cs[i-1] == '2' && cs[i] <= '6')) {
  14. val += (i-2) < 0 ? 1 : dp[i-2];
  15. }
  16. dp[i] = val;
  17. }
  18. return dp[dp.length-1];
  19. }
  20. }

由于i项的解,只依赖于i-1和i-2的状态,可以优化为常数空间。

  1. class Solution {
  2. public int numDecodings(String s) {
  3. char[] cs = s.toCharArray();
  4. // i-2状态
  5. int first = 1; // 当i=1时,如果需要访问dp[i-2]的状态,应该为1
  6. // i-1状态
  7. int second = cs[0] == '0' ? 0 : 1;
  8. for (int i = 1; i < s.length(); i++) {
  9. int val = 0;
  10. if (cs[i] != '0') { // 单个字符做映射,即字符不为0字符
  11. val += second;
  12. }
  13. // 两个字符做映射
  14. // i-1处字符为1(10-19),或者i-1处字符为2且i处字符小于等于6(20-26)
  15. if (cs[i-1] == '1' || (cs[i-1] == '2' && cs[i] <= '6')) {
  16. val += first;
  17. }
  18. first = second;
  19. second = val;
  20. }
  21. return second;
  22. }
  23. }