91. 解码方法

题目描述

一条包含字母 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 数组多使用一个空间即可。图中的数字均为下标

在多使用了一个空间后,需要注意两点:

  1. 怎么初始化 dp0dp0dp0 保证计算时后面的值不会出错:以上面的图为例,当 i=2i = 2i=2 时,要计算到 s1s1s1 的方案总数,此时会查看 s1s1s1 是否能解码,能解码就让 dp2+dp1dp2 + dp1dp2+dp1。也会查看 s1,s0s1, s0s1,s0 合起来能不能解码,能解码就让 dp2+dp0dp2 + dp0dp2+dp0,合起来能解码是一种方案,所以 dp0dp0dp0 应该初始化为 111
  2. 确定映射关系:由于 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];
    }
};
相关推荐
2401_841495643 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
fqbqrr3 小时前
2607C++,soui与安卓
c++·soui
fqbqrr7 小时前
2607C++,使用微软detours勾挂工具
c++
蓝悦无人机10 小时前
C++基础 — 函数总结
开发语言·c++
REST_302711 小时前
告别Excel孤岛:一款任务链路可视化工具带来的真实改变
大数据·微服务·云计算·编辑器·excel·动态规划
我不是懒洋洋11 小时前
从零实现一个分布式监控:Prometheus的核心设计
c++
An_s12 小时前
c++对接pdfium(一)win系统篇
开发语言·c++
众少成多积小致巨12 小时前
C++ 规范参考(上)
c++
一拳一个呆瓜15 小时前
【STL】iostream 编程:使用提取运算符
c++·stl
我不是懒洋洋16 小时前
从零实现一个分布式日志平台:ELK的核心设计
c++