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];
    }
};
相关推荐
ShineWinsu7 小时前
对于C++:C++11中lambda、function、bind的解析
c++·面试·笔试·开发·lambda·bind·function
码匠许师傅7 小时前
【C++ 面试真题】聊聊 C++ 的拷贝构造与拷贝赋值
java·c++·面试
旖旎夜光8 小时前
LeetCode 3:无重复字符的最长子串(滑动窗口) —— 题解
数据结构·c++·算法·leetcode·滑动窗口
汉字萌萌哒8 小时前
2024CSP-J入门级C++真题详解
开发语言·c++
hy.z_7778 小时前
【C++】13. 继承
c++
汉字萌萌哒9 小时前
2019CCF-CSP入门级C++试题解析
java·开发语言·c++
2401_890603409 小时前
C++ 继承
c++
果果燕10 小时前
C++ 学习笔记(二):类与对象—— 构造函数与析构函数
开发语言·c++
老洋葱Mr_Onion10 小时前
【C++】信息学奥赛CSP通关之路——CSP-J/S第一轮原创全真模拟试卷集(2026)卷四全卷解析
开发语言·c++
布莱克60511 小时前
理解 C/C++ 中的 void* 指针
c语言·c++