91. 解码方法

91. 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:

'A' -> 1
'B' -> 2
...
'Z' -> 26

给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例 1:
输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。

示例 2:
输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

解析

递归和动态规划两种解法,重点理解如何从递推转换成动态规划。

方法1:递归

从后往前递推。

算法思路:

22067 为例,从后往前遍历:

  • 首先如果为 7 ,很显然是 1 种,即 7 -> G
  • 如果为 67,很显然还是 1 种,即 67 -> FG
  • 如果为 067,结果为 0
  • 如果为 2067,结果为 numDecodings(20 67) + numDecodings(2 067) = numDecodings(20 67) -> TFG
  • 如果为 22067,结果numDecodings(2 2067) + numDecodings(22 067) = numDecodings(2 2067) -> BTFG。

从中,我们可以看出规律:

  • 如果开始的数为0,结果为 0;
  • 如果开始的数加上第二个数 <= 26, 结果为 numDecodings(start+1) + numDecodings(start+2);
  • 如果开始的数加上第二个数 > 26, 结果为 numDecodings(start+1).
参考代码1:
class Solution {
    // 递归 时间O(n) 空间O(n)
    public int numDecodings(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        return helper(s, 0);
    }
    // 递归的套路,加一个index控制递归的层次
    private int helper (String s, int start) {
        // 递归终止条件
        if (s.length() == start) {
            return 1;
        }
        // 以 0 位开始的数是不存在的
        if (s.charAt(start) == '0') {
            return 0;
        }
        /**
        * 递归的递推公司应该是如果 index 的后两位 <= 26,
        * 则:helper(s, start) = helper(s, start + 1) + helper(s, start + 2)
        * 否则:helper(s, start) = helper(s, start + 1)
        */
        int ans1 = helper(s, start + 1);
        int ans2 = 0;
        if (start < s.length() - 1) {
            int ten = (s.charAt(start) - '0') * 10;
            int one = (s.charAt(start + 1) - '0');
            if (ten + one <= 26) {
                ans2 = helper(s, start + 2);
            }
        }
        return ans1 + ans2;
    }
}
复杂度分析
  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。

方法2:动态规划

递归解法存在大量的重复计算从中可以看出,在计算中进行了大量的重复计算,因此。可以想办法将重叠子问题记录下来,避免重复计算。

引入一个数组dp[],用来记录以某个字符为开始的解码数。动态规划其实就是一个填表的过程。整个过程的目标就是要填好新增的dp[]数组。

参考代码2:从后往前推
class Solution {
    // 从后往前推
    // DP 时间O(n) 空间O(n)
    public int numDecodings(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        int len = s.length();
        int[] dp = new int[len + 1];
        dp[len] = 1;
        if (s.charAt(len - 1) == '0') {
            dp[len - 1] = 0;
        } else {
            dp[len - 1] = 1;
        }
        for (int i = len - 2; i >= 0; i--) {
            if (s.charAt(i) == '0') {
                dp[i] = 0;
                continue;
            }
            if ((s.charAt(i) - '0') * 10 + (s.charAt(i + 1) - '0') <=  26) {
                dp[i] = dp[i + 1] + dp[i + 2];
            } else {
                dp[i] = dp[i + 1];
            }
        }
        return dp[0];
    }
}
参考代码3:从前往后推
class Solution {
    /**
    * 1. 定义子问题
    *    s[i] : i-1...i 每个元素可以译码方法总数 
    * 2. 定义状态
    *    dp[i] 表示以s[i]结尾的前缀字符串有多种解码方法
    * 3. DP方程
    *    (1) 如果当前字符不为0,则当前字符解码方法总数为累加前一个前缀字符解码数 dp[i] += dp[i-1]
    *    (2) 前一个字符为1 或者 (2 且 当前字符 <= 6)
    */
    public int numDecodings(String s) {
        if(s == null) {
            return 0;
        }
        int n = s.length();
        if (n == 0 || s.charAt(0) == '0') {
            return 0;
        }
        int[] dp = new int[n];
        dp[0] = 1;
        for (int i = 1; i < n; i++) {
            // 如果当前字符不为0,则当前字符解码方法总数为累加前一个前缀字符解码数
            if (s.charAt(i) != '0') {
                dp[i] += dp[i-1];
            }
            // 前一个字符为1 或者 (2 且 当前字符 <= 6)
            if (s.charAt(i-1) == '1' || (s.charAt(i-1) == '2' && s.charAt(i) <= '6')) {
                if (i - 2 > 0) {
                    dp[i] += dp[i-2];
                } else {
                    dp[i]++;
                }
            }
        }
        return dp[n-1];
    }
}
参考代码4:空间优化

细心的话,会发现我们其实并不需要申请一个长度为len+1的数组来存储中间过程。其实dp[i]只和dp[i+1]以及dp[i+2]相关。
因此,此处可以继续空间压缩。

 class Solution {
    // 时间O(n) 空间O(1)
	public int numDecodings(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        int len = s.length();

        int help = 1;
        int res = 0;
        if (s.charAt(len - 1) != '0') {
            res = 1;
        }
        for (int i = len - 2; i >= 0; i--) {
            if (s.charAt(i) == '0') {
                help = res;
                res = 0;
                continue;
            }
            if ((s.charAt(i) - '0') * 10 + (s.charAt(i + 1) - '0') <= 26) {
                res += help;
                //help用来存储res以前的值
                help = res-help;
            } else {
                help = res;
            }

        }
        return res;
    }
 }

部分图片来源于网络,版权归原作者,侵删。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值