动态规划巧解LeetCode“解码方法”问题

题目来源: LeetCode 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)

二.解题思路

2.1 为什么会想到动态规划?

解题第一步首先是看题目的要求即计算解码方法的总数,结合示例,我们可以看出字符串s中的某个字符在编码的时候只存在两种情况

  • 单独为一个码
  • 与其之前的一个字符共同组成一个码(当前字符与其后一个字符组成的码的情况可以看作后一个字符与当前字符组合的结果,可以看作后一个字符的第二种情况)

根据上面的这种联系,我们可以发现该问题存在最优性原理,后一阶段的状态只依赖于前一状态,因此可以考虑使用动态规划。

2.2 寻找状态转移方程

假设 d p [ n − 1 ] dp[n-1] dp[n1]表示子串s[0...n-1]的编码方法总数, d p [ n − 2 ] dp[n-2] dp[n2]表示子串s[0...n-2]的编码方法总数,当前字符为s[n],则当前子串s[0...n]的编码方法总数如何计算呢?其实很简单即:
d p [ n ] = d p [ n − 1 ] + a d d n u m s dp[n] = dp[n-1] + addnums dp[n]=dp[n1]+addnums
其中 a d d n u m s addnums addnums表示的是新增的编码方法数,它可以为0,即表示没有增加新的编码方法。那新增的编码方法数从何而来呢?根据之前提到的两种情况:

  • 情况一:不需要考虑在内,因为当前字符单独编码的方法在上一个字符单独编码时已经考虑过了
  • 情况二:需要考虑,因为s[n-1]s[n-1]组合编码的情况是不可能包含在之前方法里面的,这时新增的方法数其实就是 d p [ n − 2 ] dp[n-2] dp[n2]的编码方法总数

综上可以得出状态转移方程如下:
d p [ n ] = { d p [ n − 1 ] + d p [ n − 2 ] I N T ( s [ n − 1 ] s [ n ] ) ∈ [ 1 , 26 ] 0 o t h e r s dp[n]=\begin{cases} dp[n-1] + dp[n-2] & INT(s[n-1]s[n])\in[1,26] \\ 0 & others \\ \end{cases} dp[n]={dp[n1]+dp[n2]0INT(s[n1]s[n])[1,26]others
其中 I N T ( s [ n − 1 , n ] ) INT(s[n-1,n]) INT(s[n1,n])表示s[n-1]sn[n]组成的子串转换为整数

2.3 状态初始化

为了方便从原串的第一个字符开始递推,可以考虑在原串前加上一个字符'3',即s = '3' + s。然后初始化初值为0且长度为新串长度的数组dp,并初始化dp[0]=1

2.4 考虑含0的特殊情况

本题还有一种特殊情况需要考虑,即输入的字符串s含0,当出现s[i]=0时,则此时能够实现编码的情况只有s[i-1]s[i]能够组成范围在 [ 0 , 26 ] [0,26] [0,26]的数,否则无法进行编码,为了方便理解,下面给出几个例子:

#可以进行编码的例子
s='11012321'
#无法进行编码的例子
s='0123'
s='130243'
s='1003458'

因此当出现0时,则需要判断当前字符s[i]与其之前的一个字符s[i-1]组成的子串能否形成有效编码,若不能可以直接返回0,即无法进行编码,否则 d p [ i ] = d p [ i − 2 ] dp[i]=dp[i-2] dp[i]=dp[i2]

三.算法演示视频

这里以s=12021为例,下面是算法的动态演示视频:
alg_video

四.示例程序

综合以上各点,下面给出示例代码:

from typing import List

class Solution:
    def numDecodings(self, s: str) -> int:
        condition = lambda x:(int(x) <= 26 and int(x) > 0 and x[0]!='0')
        s = '3' + s
        n = len(s)
        dp = [0] * n
        dp[0] = 1 
        for i in range(1,n):
            if s[i] == '0':
                if condition(s[i-1:i+1]):#当前字符为0但与其前一个字符能够组成合法编码
                    dp[i] = dp[i - 2]
                else:
                    return 0
            else:
                dp[i] = dp[i - 1] + ( dp[i - 2] if condition(s[i-1:i+1]) else 0)
        return dp[-1]

以上就是本文的全部内容,如果觉得不错的话就点个赞或关注一下博主吧,后续还有不断更新各种算法题解,如果发现问题也请批评指正!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

斯曦巍峨

码文不易,有条件的可以支持一下

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

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

打赏作者

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

抵扣说明:

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

余额充值