题目来源: 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[n−1]表示子串s[0...n-1]
的编码方法总数,
d
p
[
n
−
2
]
dp[n-2]
dp[n−2]表示子串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[n−1]+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[n−2]的编码方法总数
综上可以得出状态转移方程如下:
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[n−1]+dp[n−2]0INT(s[n−1]s[n])∈[1,26]others
其中
I
N
T
(
s
[
n
−
1
,
n
]
)
INT(s[n-1,n])
INT(s[n−1,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[i−2]。
三.算法演示视频
这里以s=12021
为例,下面是算法的动态演示视频:
四.示例程序
综合以上各点,下面给出示例代码:
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]
以上就是本文的全部内容,如果觉得不错的话就点个赞或关注一下博主吧,后续还有不断更新各种算法题解,如果发现问题也请批评指正!!!