「动态规划」如何计算解码方法的总数?

91. 解码方法icon-default.png?t=N7T8https://leetcode.cn/problems/decode-ways/description/一条包含字母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位的整数。

  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"并不等价)。

提示:1 <= s.length <= 100;s只包含数字,并且可能包含前导零。


我们用动态规划的思想来解决这个问题。

确定状态表示:根据经验和题目要求,我们用dp[i]表示字符串中[0, i]区间上,一共有多少种解码方法

推导状态转移方程:这里我们要根据字符串中i位置和i-1位置的情况分类讨论。

  • 如果我们把字符串中i位置的数字单独解码,解码为1个字母。
    • 如果字符串中i位置的数字在[1, 9]的范围内,则解码成功,解码方法数和字符串中[0, i - 1]区间上的解码方法数相同,即dp[i - 1]。
    • 如果字符串中i位置的数字是0,则解码失败,前面的解码作废,前功尽弃,解码方法数为0。
  • 如果我们把字符串中i - 1位置和i位置的2个数字结合起来,解码为1个字母.
    • 如果字符串中i - 1位置和i位置的2个数字结合起来的数字在[10, 26]的范围内,则解码成功,解码方法数和字符串中[0, i - 2]区间上的解码方法数相同,即dp[i - 2]。
    • 否则,解码失败,前面的解码作废,前功尽弃,解码方法数为0。

综上所述,dp[i]默认是0。若字符串中i位置的数字解码成功,dp[i] += dp[i - 1];若字符串中i - 1位置和i位置的2个数字结合起来解码成功,dp[i] += dp[i - 2]

初始化:根据状态转移方程,dp[0]和dp[1]是会越界的。我们在dp表的最左边加上一个辅助结点。辅助结点的特点有:

  • 下标的映射关系:由于dp表的其他位置整体向右移动了1个单位,所以dp表的i位置对应字符串的i - 1位置
  • 辅助结点里面的值要保证后续填表是正确的。

如何保证后续填表是正确的呢?

  • dp[1]的值要根据字符串中0位置的数字是否能解码来决定。如果字符串中0位置的数字是0,那么就不能解码,dp[1] = 0;否则dp[1] = 1。
  • dp[2]的值如果要满足状态转移方程,关键在于状态转移方程的后半句话:若字符串中0位置和1位置的2个数字结合起来解码成功,dp[2] += dp[0],而此时dp[2] += 1是最合适的,从而倒推出dp[0] = 1。

综上所述,dp[0] = 1,dp[1] = s[0] == '0' ? 0 : 1

填表顺序:根据状态转移方程,dp[i]依赖于dp[i - 1]和dp[i - 2],所以应从左往右填表

返回值:根据状态表示,应返回dp[n],对应字符串中[0, n - 1]区间上的解码方法数,注意此时dp表的n位置对应字符串的n - 1位置。

细节问题:由于下标范围是[0, n],dp表的规模应该为1 x (n + 1)

时间复杂度:O(N),空间复杂度:O(N)。

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        // 创建dp表
        vector<int> dp(n + 1);

        // 初始化
        dp[0] = 1;
        dp[1] = s[0] == '0' ? 0 : 1;

        // 填表
        for (int i = 2; i <= n; i++) {
            // 最后一个位置解码
            dp[i] += s[i - 1] == '0' ? 0 : dp[i - 1];
            // 最后两个位置结合起来解码
            int num = (s[i - 2] - '0') * 10 + s[i - 1] - '0';
            dp[i] += num >= 10 && num <= 26 ? dp[i - 2] : 0;
        }

        // 返回结果
        return dp[n];
    }
};
  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习游泳的鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值