• LeetCode 91,点赞和反对五五开,这题是好是坏由你来评判


    本文始发于个人公众号:TechFlow,原创不易,求个关注


    今天是LeetCode专题的第57篇文章,我们一起来看看LeetCode第91题,解码方法(Decode ways)。

    这道题官方给定的难度是Medium,点赞2680,反对2845,通过率24.5%。从通过率上来看这道题似乎很难,甚至比很多Hard难度的问题还要难。从点赞和反对的数量来看,这道题应该算是褒贬不一,两边阵营的人数都很多。一般来说题目的评价都是一边倒为主,这种情况很少出现。首先值得一说的是,这道题我个人肯定认为是值得一做的,至于它是否是一道好问题呢?这个问题就留给大家自己回答吧。

    首先我们来看下这道题的题意。

    题意

    在传送消息的时候我们可以把字母映射成数字,比如A映射成1,B映射成2,Z映射成26。我们这里假设用到的字母只有大写的,也就是说我们用26个数字就可以代表A到Z的所有字母

    现在我们得到了一串数字连成的字符串,想要将它还原成原本字母组成的信息,请问它一共有多少种还原的方法?

    样例

    Input: "12"
    Output: 2
    Explanation: It could be decoded as "AB" (1 2) or "L" (12).
    Input: "226"
    Output: 3
    Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).

    题解

    我们观察一下样例,比如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,那么判断前一位是否是1或2
                # 否则一定无解
                if s[i] == '0':
                    if i > 1 and s[i-1in ('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。

    我不清楚给差评的是否都是后一种情况,但单纯从题目的质量上来说,这道题的质量是不错的,是一道很不错的联系动态规划的习题,因此建议大家有时间都能体会一下。

    今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。

    - END -

  • 相关阅读:
    C++
    C语言获取系统时间的几种方式
    我在学习编程中犯的两个最大错误
    《十天学会单片机和C语言编程》
    socket 编程通信实例
    比尔·盖茨给年轻人的十一个忠告 句句是哲理
    程序员有八个级别。
    DOS debug 命令的详细用法
    从命令行模式运行Windows管理工具。
    无法删除DLL文件解决方法(转)
  • 原文地址:https://www.cnblogs.com/techflow/p/13495254.html
Copyright © 2020-2023  润新知