【动态规划】LeetCode-91.解码方法

🎈算法那些事专栏说明:这是一个记录刷题日常的专栏,每个文章标题前都会写明这道题使用的算法。专栏每日计划至少更新1道题目,在这立下Flag🚩

🏠个人主页:Jammingpro

📕专栏链接:算法那些事

🎯每日学习一点点,技术累计看得见

题目

题目描述

一条包含字母 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 只包含数字,并且可能包含前导零。

题解

首先,我们通过上面的解码规则,对"123"进行解码。"1"、"2"、"3"均能够单独解码,这是一种解码方法;将"1"和"2"组合解码为"12",其范围在1到26之间,故解码成功,因此"12"、"3"是一种解码方法;将"1"单独解码,"2"和"3"组合解码为"23",其范围在1到26之间,故也解码成功,因此"1"、"23"也是一种解码方法。所以"123"共有3种解码方法。

再看一个示例:对"1032"进行解码。"1"、"0"、"3"、"2"无法分开单独解码,因为"0"不在1到26范围内,因此,"0"需要与"1"或"3"组合解码。"1"和"0"组合解码为"10",在1到26之间;"0"和"3"组合解码为"03",因为其包含前导0,故不满足题目要求。因此,"0"必须与"1"组合解码。"3"和"2"可以分开解码,但无法组合解码,因为其组合为"32",超出26,故解码失败。这个字符串只有一种解码方法,即1032

由上面分析可知,遇到①组合数有前导0、②构成数不在1到26之间,将会导致解码失败。除此之外,出现连续两个0,即"00",也会解码失败。

我们可以s[i]和s[i-1]是否为0做分类分析:

若s[i]=='0',则我们再判断s[i-1]是否为'0',如果s[i]==0&&s[i-1]==0,则出现连续两个0,这种排列方式无法解码,直接返回0;如果s[i]==0&&s[i-1]!=0,即s[i]无法单独解码,需要与s[i-1]组合在一起才能解码,如果s[i]与s[i-1]组合数<=26,则dp[i]=dp[i-2],否则return 0(即两个字符无法分开解码,也无法合在一起解码)。

ps:为什么这里dp[i]=dp[i-2]呢?dp[i-2]中保存的是从0号字符到i-2号字符的解码方法。因为上述情况只能组合解码,因此s[i]与s[i-1]需组合解码到一起,所以dp[i]的解码方法与dp[i-2]的解码方法相同。

若s[i]!='0',则我们再判断s[i-1]是否为'0',如果s[i]!=0&&s[i-1]==0,则两个字符无法一起解码,需要s[i-1]与s[i-2]各自解码,因此dp[i]=dp[i-1](此时出现前导0的情况,前导0无法和s[i]组合,但可以与s[i-2]组合;如果无法与s[i-2]组合,则上一次迭代已经return 0了);如果s[i]!='0'&&s[i-1]!='0',若两个数字组合数<=26,则dp[i]=dp[i-1]+dp[i-2],若组合数>26,则dp[i]=dp[i-1]。

ps:这里怎么有dp[i]=dp[i-1]+dp[i-2]?因为s[i]能和s[i-1]组合解码,其组合解码后,s[0-i]的解码方法数与dp[i-2]相同;s[i]和s[i-1]分开解码,则s[0-i]阶解码方法数与dp[i-1]相同,故得到上述式子。

经过上面的分析,我们可以得到如下代码↓↓↓

cpp 复制代码
class Solution {
public:
    int numDecodings(string s) {
        if(s[0] == '0') return 0;
        int n = s.size();
        if(n == 1) return 1;
        vector<int>dp(n);
        dp[0] = 1;
        if(s[1] == '0')
        {
            if((s[0] - '0') * 10 + (s[1] - '0') <= 26)
                dp[1] = 1;
            else
                return 0;
        }
        else
        {
            if((s[0] - '0') * 10 + (s[1] - '0') <= 26)
                dp[1] = 2;
            else
                dp[1] = 1;
        }

        for(int i = 2; i < n; i++)
        {
            if(s[i] == '0' && s[i - 1] == '0') return 0;
            if(s[i] == '0')
            {
                if((s[i - 1] - '0') * 10 + (s[i] - '0') <= 26)
                {
                    if(s[i - 1] == '0')
                        dp[i] = dp[i - 1];
                    else
                        dp[i] = dp[i - 2];
                }
                else
                    return 0;
            }
            else
            {
                if((s[i - 1] - '0') * 10 + (s[i] - '0') <= 26)
                {
                    if(s[i - 1] == '0')
                        dp[i] = dp[i - 1];
                    else
                        dp[i] = dp[i - 1] + dp[i - 2];
                }
                else
                    dp[i] = dp[i - 1];
            }
        }
        return dp[n - 1];
    }
};

这个代码看起来非常繁琐,嵌套了需要分支语句,可阅读性较差。下面我们对这个代码做一些优化↓↓↓

cpp 复制代码
class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        vector<int>dp(n + 1);
        dp[0] = 1;//这里没有实质性含义,因为s[0]与s[1]组合数若满足10-26,需要+dp[0],故初始化为1
        dp[1] = s[0] != '0';
        for(int i = 2; i <= n; i++)
        {
            int t = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');//计算组合数
            dp[i] = s[i - 1] != '0' ? dp[i - 1] : 0;//当前字符不为0,则可以单独解码,则dp[i]=dp[i-1]
            dp[i] += t >=10 && t <= 26 ? dp[i - 2] : 0;//组合数在10-26之内,则+dp[i-2]
        }
        return dp[n];
    }
};

优化代码中的dp下标与s下标差1,代码思路与前一代码类似,但更为巧妙。

本文存在不足,欢迎留言或私信批评、指正。希望我的解决方法能够对你有所帮助~~

今日打卡完成,点亮小星星☆→★

相关推荐
我要学编程(ಥ_ಥ)3 分钟前
一文详解“二叉树中的深搜“在算法中的应用
java·数据结构·算法·leetcode·深度优先
埃菲尔铁塔_CV算法4 分钟前
FTT变换Matlab代码解释及应用场景
算法
许野平1 小时前
Rust: enum 和 i32 的区别和互换
python·算法·rust·enum·i32
chenziang11 小时前
leetcode hot100 合并区间
算法
chenziang11 小时前
leetcode hot100 对称二叉树
算法·leetcode·职场和发展
szuzhan.gy1 小时前
DS查找—二叉树平衡因子
数据结构·c++·算法
一只码代码的章鱼2 小时前
排序算法 (插入,选择,冒泡,希尔,快速,归并,堆排序)
数据结构·算法·排序算法
青い月の魔女2 小时前
数据结构初阶---二叉树
c语言·数据结构·笔记·学习·算法
林的快手3 小时前
209.长度最小的子数组
java·数据结构·数据库·python·算法·leetcode
千天夜3 小时前
多源多点路径规划:基于启发式动态生成树算法的实现
算法·机器学习·动态规划