​LeetCode刷题实战91:解码方法

共 2229字,需浏览 5分钟

 ·

2020-11-10 20:27

算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !

今天和大家聊的问题叫做 解码方法,我们先来看题面:

https://leetcode-cn.com/problems/decode-ways/

A message containing letters from A-Z is being encoded to numbers using the following mapping:

题意



一条包含字母 A-Z 的消息通过以下方式进行了编码:
'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
题目数据保证答案肯定是一个 32 位的整数。

样例

示例 1

输入:"12"
输出:2
解释:它可以解码为 "AB"1 2)或者 "L"12)。

示例 2

输入:"226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

示例 3

输入:s = "0"
输出:0

示例 4

输入:s = "1"
输出:1

示例 5

输入:s = "2"
输出:1


解题

https://www.cnblogs.com/techflow/p/13489811.html

题解

我们观察一下样例,比如12可以理解成12这个字符也就是L,也可以理解成1和2两个字符拼接而成。同样226有三种还原的方法,分别是(2, 2, 6), (22, 6), (2, 26)。我们只需要返回所有可能的数量即可。
这道题看起来没有头绪,但是当我们仔细分析一下其中的情况,其实并不复杂。首先对于字符串当中的每一位来说,只有0到9这10种可能性。我们逐一来考虑,首先如果是0,由于我们的A映射的是1,所以0只能和前面一个数字凑成10或者是20,如果前一位不是1或者是2,那么说明这个字符串是非法的,也就是没有办法还原。
如果某一位是1-9当中的数字,它都可以单独成为一个字母。我们再考虑它的前一位,它的前一位只有1和2这两种可能可以构成新的组成。这里要注意,如果它的前一位是2的话,那么当前位必须要小于6,因为英文字母最多只到26。所以这里也需要进行一个特殊判断,除此之外,就没有其他情况了。
这些可能性都列举出来之后,剩下的就简单了,比如我们可以用深搜来搜索所有解的可能性。由于我们已经列举出了所有的情况,所以这段代码并不难写,但是想要一次就写对也不容易。

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        ret = 0
        
        def dfs(i):
            nonlocal ret
            
            if i >= n:
                ret += 1
                return 
            
            # 当前位如果是0则说明无解,return
            # 当前位为0说明上一位不是1或者2
            if s[i] == '0':
                return
            
            # 递归单个的情况
            dfs(i+1)
            
            # 判断下一位能否构成组合
            if i+1 < n and (s[i] == '1' or (s[i] == '2' and ord(s[i+1]) <= ord('6'))):
                dfs(i+2)
                
        dfs(0)
        return ret


搜索算法固然可以解,但是一定会超时。这一点也不难想明白,当我们的字符串长度大了之后,带来的解的可能性是非常多的。最极端的情况下,比如每一位都是1,它都可以单独作为一个字符也可以和下一位组合成11构成新的字符。这样的情况总数是以斐波那契数列递增的,n不需要多大带来的解的数量就是天文数字了。
使用搜索算法我们需要穷举每一种情况,哪怕寻找每一个解只需要的复杂度也是无法抗住的。
所以我们必须要想一些更优的方法,这个方法也不难想,就是动态规划
我们分析一下会发现每一位数字能够组成的解只和它的前一位有关,和后面的都没有关系。这样的话显然是满足动态规划的无后效性的。也就是说前面的字符组合的情况不会影响后面的解。
我们假设dp[i]存储的是前i个数字构成的解的数量,对于s[i]来说有10种情况,分别是0到9。如果s[i]为0,那么s[i-1]如果是1或者是2的话,只有一种情况,就是0和s[i-1]组成10或者是20。那么dp[i] = dp[i-2]。
如果s[i]不为0,那么s[i]可以选择单独成为一个字符,那么dp[i] = dp[i-1]。当然如果s[i-1]是1或者是2的话,s[i]也可以选择和s[i-1]合起来组成一个字符。那么这样的情况下,dp[i]需要再额外增加dp[i-2],也就是dp[i]构成的答案可能性增加了。
如果把这些状态之间的转移情况都梳理清楚了,那么这个代码肯定不难写的。

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        # 为了简化判断,我们把s前面加上0,这样字符串下标从1开始
        s = '0' + s
        dp = [0 for _ in range(n+2)]
        
        dp[0] = 1
        for i in range(1, n+1):
            # 如果当前位0,那么判断前一位是否是12
            # 否则一定无解
            if s[i] == '0':
                if i > 1 and s[i-1] in ('1', '2'):
                    dp[i] = dp[i-2]
                else:
                    return 0
                continue
            dp[i] = dp[i-1]
            # 能和前一位构成字符,那么加上dp[i-2]的数量
            if i > 1 and s[i-1] == '1' or s[i-1] == '2' and ord(s[i]) <= ord('6'):
                dp[i] += dp[i-2]
        return dp[n]

总结

从动态规划的角度上来看,这道题并不算困难,说是迎刃而解也不为过。但如果没有想到动态规划,纠结于搜索算法的话那么可能一直都没有办法AC。
我不清楚给差评的是否都是后一种情况,但单纯从题目的质量上来说,这道题的质量是不错的,是一道很不错的联系动态规划的习题,因此建议大家有时间都能体会一下。
好了,今天的文章就到这里,如果觉得有所收获,请顺手点个在看或者转发吧,你们的支持是我最大的动力。


上期推文:

LeetCode50-80题汇总,速度收藏!
LeetCode刷题实战81:搜索旋转排序数组 II
LeetCode刷题实战82:删除排序链表中的重复元素 II
LeetCode刷题实战83:删除排序链表中的重复元素
LeetCode刷题实战84: 柱状图中最大的矩形
LeetCode刷题实战85:最大矩形
LeetCode刷题实战86:分隔链表
LeetCode刷题实战87:扰乱字符串
LeetCode刷题实战88:合并两个有序数组
LeetCode刷题实战89:格雷编码
LeetCode刷题实战90:子集 II

浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报