本文始发于个人公众号: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-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。

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

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

- END -

LeetCode 91,点赞和反对五五开,这题是好是坏由你来评判的更多相关文章

  1. leetcode@ [91] Decode Ways (Dynamic Programming)

    https://leetcode.com/problems/decode-ways/ A message containing letters from A-Z is being encoded to ...

  2. Leetcode 91. Decode Ways 解码方法(动态规划,字符串处理)

    Leetcode 91. Decode Ways 解码方法(动态规划,字符串处理) 题目描述 一条报文包含字母A-Z,使用下面的字母-数字映射进行解码 'A' -> 1 'B' -> 2 ...

  3. [转帖]鲁大师Q3季度PC处理器排行:AMD、Intel终于五五开了

    鲁大师Q3季度PC处理器排行:AMD.Intel终于五五开了 https://www.cnbeta.com/articles/tech/902375.htm 近日,鲁大师发布了Q3季度PC处理器排行. ...

  4. 《学习OpenCV》练习题第五章第二题abc

    代码: #include <stdio.h> #include <opencv/highgui.h> #include <opencv/cv.h> #include ...

  5. 《学习OpenCV》练习题第五章第一题ab

    这道题是载入一幅带有有趣纹理的图像并用不同的模板(窗口,核)大小做高斯模糊(高斯平滑),然后比较用5*5大小的窗口平滑图像两次和用11*11大小的窗口平滑图像一次是否接近相同. 先说下我的做法,a部分 ...

  6. 20155326 第五周加分题--mybash的实现

    第五周加分题--mybash的实现 题目要求 1.使用fork,exec,wait实现mybash 2.写出伪代码,产品代码和测试代码 3.发表知识理解,实现过程和问题解决的博客(包含代码托管链接) ...

  7. 第五周加分题--mybash的实现

    第五周加分题--mybash的实现 题目要求 1.使用fork,exec,wait实现mybash 2.写出伪代码,产品代码和测试代码 3.发表知识理解,实现过程和问题解决的博客(包含代码托管链接) ...

  8. 第五周 加分题-mybash的实现

    第五周 加分题-mybash的实现 使用fork,exec,wait实现mybash 产品代码 #include <stdio.h> #include <stdlib.h> # ...

  9. 2017-2018-1 20155320 第五周 加分题-mybash的实现

    2017-2018-1 20155320 第五周 加分题-mybash的实现 使用fork,exec,wait实现mybash 写出伪代码,产品代码和测试代码 发表知识理解,实现过程和问题解决的博客( ...

随机推荐

  1. echarts 实战 : 怎么写出和自动生成的一样的 tooltip ?

    找到答案很麻烦,但答案本身很简单. 假设 需要给 echarts 的数据是 option. option.tooltip.formatter = (params) => { return `&l ...

  2. 浅谈6种JS数组遍历方法的区别

    本篇文章给大家介绍一下6种JS数组遍历方法:for.foreach.for in.for of.. each. ().each的区别.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. ...

  3. .net core 使用 Serilog 作为日志提供者

    nuget引入 Serilog.AspNetCore Startup构造函数: public Startup(IConfiguration configuration) { Configuration ...

  4. DC-1靶机实战和分析

    前言 我们都知道,对靶机的渗透,可以宽阔自己的解题思路,练习并熟悉相关操作命令,提高自己的能力.下面我就对Vulnhub的DC-1靶机进行渗透,靶机设置了5个flag,咱们依次找到它.并通过图文形式讲 ...

  5. Unity 基于excel2json批处理读取Excel表并反序列化

    excel2json是一款将Excel表格文件快速生成json和C#数据类的高效插件,详情了解如下: https://neil3d.github.io/coding/excel2json.html 该 ...

  6. Ansible基础

    Ansible基于Python paramiko 开发,分布式,无需客户端,轻量级,配置语法使用YMAL 及Jinja2模板语言. 组件: 核心:ansible 核心模块(Core Modules): ...

  7. c++ string类find总结

    c++ string类的find:1.find string s = "My cat's breath smells like cat food."; int a=s.find(& ...

  8. PHP imagecolorallocate - 为一幅图像分配颜色

    imagecolorallocate — 为一幅图像分配颜色.高佣联盟 www.cgewang.com 语法 int imagecolorallocate ( resource $image , in ...

  9. PHP convert_cyr_string() 函数

    实例 把字符串由一种字符集转换成另一种: <?php$str = "Hello world! æøå";echo $str . "<br>"; ...

  10. C/C++编程笔记:C++入门知识丨函数和函数模板

    本篇要学习的内容和知识结构概览 函数的参数及其传递方式 1. 函数参数传递方式 传值: 传变量值: 将实参内存中的内容拷贝一份给形参, 两者是不同的两块内存 传地址值: 将实参所对应的内存空间的地址值 ...