[Leetcode 91] Dynamic Programming - Decode Ways(解码方法)

d p [ i ] dp[i] dp[i]表示从左到右取 s s s的长度为 i + 1 i+1 i+1的子串,这个子串有多少种可解码方式。

dp的公式应该是:

  • 如果以 d p [ i − 1 ] dp[i-1] dp[i1]为十位和以 d p [ i ] dp[i] dp[i]为个数组成的数字大于等于1小于等于26
    d p [ i ] = d p [ i − 2 ] + d p [ i − 1 ] dp[i]=dp[i-2]+dp[i-1] dp[i]=dp[i2]+dp[i1]
  • 当不满足上述条件:
    d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i1]

这个问题中,需要考虑一个问题:当某个值为0的问题,如果可以组成10和20两个数字,则有解码方法;否则就不存在解码方式,返回0。

综合以上两点,我的解题思路是:

  1. 首先遍历一遍输入的字符串,判断所有的0是否合法,即是否都可以和前一个字符组成10或者20,有一个不行则不存在解码方法,直接返回0,程序结束,否则进行下一步
  2. 上一步保证了输入的字符串至少会存在一种解码方法,所以这里不用再考虑不合法的问题。首先对dp数组进行初始化:
    1. 因为题目限定了输入非空,至少为一,所以直接对只有输入长度为1的情况进行初始化: d p [ 0 ] = 1 dp[0] = 1 dp[0]=1
    2. 如果输入字符串长度大于等于2,则首先对长度为2的情况进行初始化:
      1. 如果 s [ 1 ] = = 0 s[1] == 0 s[1]==0:则 d p [ 1 ] = 1 dp[1]=1 dp[1]=1
      2. 如果 s [ 1 ] ! = 0 s[1] != 0 s[1]!=0:
        1. 如果以 s [ 0 ] s[0] s[0]为十位,以 s [ 1 ] s[1] s[1]为个位,可以构成 [ 11 , 19 ] [11,19] [11,19] [ 21 , 26 ] [21,26] [21,26]两个区间中的任何一个整数,则 d p [ 1 ] = 2 dp[1]=2 dp[1]=2
        2. 否则 d p [ 1 ] = 1 dp[1]=1 dp[1]=1
  3. 我们只需要对dp数组的前两个元素进行初始化,接下来采用dp算法求解:
    1. 如果 s [ i ] = = 0 s[i] == 0 s[i]==0:则 s [ i ] s[i] s[i]必定只能和前一个数字构成一个两位数 10 10 10或者 20 20 20,此时有 d p [ 1 ] = 1 dp[1]=1 dp[1]=1
    2. 如果 s [ i ] ! = 0 s[i] != 0 s[i]!=0: 则当前数字一定可以单独构成一个数字或者和前一个数字构成一个两位数
      1. 如果以 s [ i − 1 ] s[i-1] s[i1]为十位,以 s [ i ] s[i] s[i]为个位,可以构成 [ 11 , 19 ] [11,19] [11,19] [ 21 , 26 ] [21,26] [21,26]两个区间中的任何一个整数,则 d p [ i ] = d p [ i − 2 ] + d p [ i − 1 ] dp[i]=dp[i-2]+dp[i-1] dp[i]=dp[i2]+dp[i1]
      2. 否则 s [ i ] s[i] s[i]只能单独构成一个数字而不能和前一个数字构成一个两位数,此时有 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i1]

具体实现代码如下:

import numpy as np

class Solution(object):
    def numDecodings(self, s):
        """
        :type s: str
        :rtype: int
        """
        
        # 先校验一下出现的所有的0是否都合法,如果存在不合法的,则直接返回0:出现零就必须可以组成10或者20,否则不合法
        for index, ch in enumerate(s):
            if ch == '0':
                if index == 0:  # 首元素就为0,肯定不存在解码方法,直接返回0
                    return 0
                else:
                    if not (s[index-1] == '1' or s[index-1] == '2'):
                        return 0
        
        # 接下来的处理过程,保证了至少有一种解码方式,因为前面的检验过程已经保证了出现了0的话,0的前面只能是1或者2
        dp = np.zeros(len(s))
        
        dp[0] = 1  # 至少会有一个字符
        
        if len(s) >= 2:  # 如果有两个字符,则第一个肯定不是0,是0的话前面处理就直接返回0了
            
            if s[1] == '0':  
                dp[1] = 1
            else:
                # 可以组成两位数的情况应该是10到19和20到26两个区间
                if (s[0] == '1' and '1' <= s[1] <= '9') or (s[0] == '2' and '1' <= s[1] <= '6'):
                    dp[1] = 2
                else:
                    dp[1] = 1
        
            for i in range(2, len(s)):
                
                if s[i] == '0':  # 如果当前字符是0,则当前字符必须和前一个字符组成10或者20
                    dp[i] = dp[i-2]
                else:  # 如果当前字符不为0,则当前字符一定可以单独存在,然后在验证它是否可以和前面一位组成一个两位数
                    
                    if (s[i-1] == '1' and '1' <= s[i] <= '9') or (s[i-1] == '2' and '1' <= s[i] <= '6'):  # 如果可以和前一个字符组成一个两位数 
                        dp[i] = dp[i-1] + dp[i-2]
                    else:  # 如果当前字符不能和前一个字符组成一个两位数,则解码时只能单独存在
                        dp[i] = dp[i-1]
                
        return int(dp[len(s)-1])

solution = Solution()
print(solution.numDecodings("17"))  



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值