91. Decode Ways(解码方法)两种解法(C++ & 注释)

2023-05-16

91. Decode Ways(解码方法)

  • 1. 题目描述
  • 2. 暴力破解(Brute Force, Time Limit Exceeded)
    • 2.1 解题思路
    • 2.2 实例代码
  • 3. 动态规划(Dynamic Programming)
    • 3.1 解题思路
    • 3.2 实例代码
  • 4. 参考资料
  • 5. 可爱的小告示哒~

1. 题目描述

一条包含字母 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. 暴力破解(Brute Force, Time Limit Exceeded)

2.1 解题思路

我们换一种思路来看待这道题:题目要求要么分解成单个字符,要么分解成两个字符,整个字符串分解完算一种拆解方法。那么这道题可以看成向前走一步或者走两步,如果能从序号0走到末尾序号,则算一种拆解方式。

说到这里,相似的题目我们见了不少呢:

  1. 79. Word Search(单词搜索)
  2. 62. Unique Paths(不同路径)
  3. 63. Unique Paths II(不同路径 II)
  4. 64. Minimum Path Sum(最小路径和)

所以我们可以用相似的思路来做这道题,但是有两个障碍条件:1)s[i] != ‘0’;2)9 < s[i, i + 1] < 27;前者表示单个字符不能为0,或两个字符首字符不能为0,比如"01";后者表示两个字符代表的数字必须大于9,小于27;

接着使用Hash Table来查找两个字符所代表的数字是否合法,所以有如下步骤:

  1. s[i] != 0,向后走一步:idx + 1;反之查找两个字符代表的数字是否合法,合法则向后走两步:idx + 1;
  2. 不能往后走一步或两步,则返回上一级;
  3. 如果 idx == s.size(),表示字符串被拆解完毕,总计数(count)++;

不用想这种方法肯定超时,这部分代码对应:2.2.1 暴力破解

但是有优化的余地:关于上面两个障碍条件,我们可以记录下来,如果下次遇到直接跳过,这样可以节约一定的时间。

1)s[i] != ‘0’:我们发现对于idx,只要后面有一个0,则不需要往后走了,所以先遍历string一遍,把为0的序号记录下来,走一步之前先二分查找是否后面有0,如果有则检查走两步;没有才继续往后走一步;
2)对于不满足s[i] == ‘0’;2)9 < s[i, i + 1] < 27,记录下idx,下次遇到直接跳过;

但是这种优化方法对于没有0的情况不是很理想,所以还是会超时。这部分代码对应:2.2.2 记忆(Memorization)

备注:upper_bound 和 lower_bound两个函数的使用方法可以参考这篇文章 - 34. 在排序数组中查找元素的第一个和最后一个位置

2.2 实例代码

2.2.1 暴力破解

class Solution {
    unordered_set<string> decode;
    int count = 0;

    void backtracking(string& s, int idx) {
        if (idx == s.size()) { count++; return; }

        if (s[idx] != '0') backtracking(s, idx + 1);
        if (idx != static_cast<int>(s.size() - 1) && decode.count(s.substr(idx, 2))) backtracking(s, idx + 2);
    }
public:
    Solution() {
        this->decode = {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26" }; // "0X"这种类型不算
    }

    int numDecodings(string s) {
        backtracking(s, 0);
        return this->count;
    }
};

2.2.2 记忆(Memorization)

class Solution {
    unordered_set<string> decode;
    unordered_set<int> twoStepsProhibited;
    vector<int> oneStepsProhibited;
    int count = 0;

    void backtracking(string& s, int idx) {
        if (idx == s.size()) { count++; return; }

        if (lower_bound(oneStepsProhibited.begin(), oneStepsProhibited.end(), idx) == oneStepsProhibited.end()) backtracking(s, idx + 1);

        if (idx != static_cast<int>(s.size() - 1) && !twoStepsProhibited.count(idx) && decode.count(s.substr(idx, 2))) backtracking(s, idx + 2);
        else twoStepsProhibited.insert(idx);
    }
public:
    Solution() {
        this->decode = { "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26" }; // "0X"这种类型不算
    }

    int numDecodings(string s) {
        // s[i] == '0'的序号
        for (int i = 0; i < s.size(); i++)
            if (s[i] == '0') oneStepsProhibited.push_back(i);

        backtracking(s, 0);
        return this->count;
    }
};

3. 动态规划(Dynamic Programming)

3.1 解题思路

把这道题看成路径问题,那么动态规划是一个非常实用的方法,那来看看这道题如何利用DP来解题。根据上面的分析,某个位置所有的拆解总数等于后两个位置的总数:memo[i] = memo[i + 1] + memo[i + 2]。比如下面这个例子:

[1,2,3]

3有一种拆解方式,2有两种拆解方式:“2”, “3” 和 “23”,那么1的拆解方法总数等于2的总数 + 3的总数: 1 + 2 = 3;这是因为:如果1作为一个数字,那么拆解总数看2和3,也就是2的总数目;如果1和2作为一个数字,那么只有3一种可能,所以memo[i] = memo[i + 1] + memo[i + 2];到这里我们明白了动态规划的公式,还差初始化条件,根据上面的分析,我们可以用len - 1 和 len - 2来作为起始状态,但是这样会有很多情况,比如"0"或"01"等不合法的情况,所以初始化len + 1个位置,memo[len] = 1,memo[len - 1]根据是否为0来判断是为1还是为0,从memo[len - 2]开始使用上面得到的公式进行计算即可;

同样,遇到0则不可能拆解,所以直接跳过;如果可能拆解成2位数,则memo[i] = memo[i + 1] + memo[i + 2];反之,则memo[i] = memo[i +1];至此动态规划的思路理解完毕,代码则有三种写法:

1)把上面的暴力破解的代码加入动态规划的思路,从后往前计算出一个idx的数目,存储起来,后面再次访问则可以直接返回对应数目;不过这个方法Java不超时,但是C++超时,对应Java代码见这里:[Java] Recursion, Top Down and Bottom Up。此部分对应代码:3.2.1 递归(Recursion, Time Limit Exceeded)

2)直接把上面的动态规划思路翻译成代码,用memo数组来储存之前的状态;此部分对应代码:3.2.2 迭代 + 额外空间(Iteration + Extra Space)

3)省略memo数组:因为公式memo[i] = memo[i + 1] + memo[i + 2] 或者 memo[i] = memo[i +1],所以至多需要之前两个状态即可,再之前的都可以省去,根据这个思路,可以不使用额外空间;我们初始化first表示memo[i + 1],second表示memo[i + 2],digit表示当前的位数,preDigit表示上一轮的位数,整个思路流程示意如下图。此部分对应代码:3.2.3 迭代 + 无需额外空间(Iteration + Without Extra Space)
在这里插入图片描述
备注:关于stringstream的用法,可以参考这篇文章 - c++ 数字与字符串的相互转换

3.2 实例代码

3.2.1 递归(Recursion, Time Limit Exceeded)

class Solution {
    vector<int> memo;

    int recursionMethodTopDown(string& s, int idx) {
        if (memo[idx] > -1) memo[idx];
        if (idx == s.size()) return 1;

        int digit = s[idx] - '0';
        int ans = digit > 0 ? recursionMethodTopDown(s, idx + 1) : 0; // memo[i] = s[i] == '0' ? memo[i + 1] : 0;
        if (idx != static_cast<int>(s.size() - 1) && digit > 0) 
            ans += (digit * 10 + s[idx + 1] - '0') < 27 ? recursionMethodTopDown(s, idx + 2) : 0; // memo[i] += (s[i] != '0' && s[i, i + 1] < 27) ? memo[i + 2] : 0;

        memo[idx] = ans;
        return ans;
    }

public:
    int numDecodings(string s) {
        this->memo = vector<int>(s.size() + 1, -1);
        return recursionMethodTopDown(s, 0);
    }
};

3.2.2 迭代 + 额外空间(Iteration + Extra Space)

class Solution {
    // 将string转换为int
    int convertToInt(string str) {
        stringstream ss;
        int num;
        ss << str;
        ss >> num;
        return num;
    }

public:
    int numDecodings(string s) {
        int len = s.size();
        if (!len) return 0;
        vector<int> memo(len + 1, 0);
        memo[len] = 1;
        memo[len - 1] = s[len - 1] == '0' ? 0 : 1;

        for (int i = len - 2; i >= 0; i--) {
            if (s[i] == '0') continue;
            else memo[i] = convertToInt(s.substr(i, 2)) <= 26 ? memo[i + 1] + memo[i + 2] : memo[i + 1];
        }

        return memo[0];
    }
};

3.2.3 迭代 + 无需额外空间(Iteration + Without Extra Space)

class Solution {
public:
    int numDecodings(string s) {
        int len = s.size(), second = 0, first = 1, ans = 0, digit = -1, preDigit = 27;

        for (int i = len - 1; i >= 0; i--) {
            digit = s[i] - '0';
            ans = digit > 0 ? first : 0; // memo[i] = s[i] == '0' ? memo[i + 1] : 0;
            ans += (digit > 0 && (digit * 10 + preDigit) < 27) ? second : 0; // memo[i] += (s[i] != '0' && s[i, i + 1] < 27) ? memo[i + 2] : 0;
            second = first; first = ans; // 存储本轮的memo[i] 和 memo[i + 1],即下一轮的memo[i + 1] 和 memo[i + 2];
            preDigit = digit;
        }

        return ans;
    }
};

4. 参考资料

  1. DP Solution (Java) for reference
  2. [Java] Recursion, Top Down and Bottom Up

5. 可爱的小告示哒~

这个星期开始研一就要开课啦,所以后面题目更新就比较佛系啦。如果大家想看某道题的解析,可以留言或者私信微信(fengkeyleaf)、QQ(402951678)都可以啦,我会把解法代码和详细思路尽量整理发出来哒,争取每个人都能看懂一道题究竟是怎么解决哒,杜绝似懂非懂的理解哒~


在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

91. Decode Ways(解码方法)两种解法(C++ & 注释) 的相关文章

  • 使用 avcodec_decode_audio4() 解码 AAC 时出错

    我尝试使用 FFmpeg 本机解码器解码 AAC 并遇到错误 SSR is not implemeted Update your FFmpeg version to newest from Git If the problem still
  • python 西里尔字母解码

    我正在尝试打印从 mysql 选择的西里尔字符 这是我的代码 内容 ID DB 是 cp1251 gt gt gt db MySQLdb connect host localhost user XXX passwd XXXX gt gt g
  • Python - 获取命令输出无法解码

    我目前正在开发一个项目 我需要在 powershell 中运行命令 并且部分输出不是英语 特别是希伯来语 例如 问题的简化版本 如果我想获取桌面的内容 并且有一个希伯来语文件名 import subprocess command power
  • 有没有办法对闭包进行编码和解码?

    由于某种原因 我需要序列化一个包含多个闭包字段的类对象 像这样 import Foundation class Foo NSObject NSCoding var bar gt Void override init bar required
  • WebSphere Portal 解码 URL

    如何解码 WebSphere Portal url 例如此网址 wps portal ut p c5 dY7LdoIwAAW hS9ICEnEZSBaKBSKkUfZcAKtKRYMKo 2X197XHtnObO4oAQ3TnJulRxbf
  • 快速将 json 解码为通用数组或类

    如何在 swift 中将 json 解码为通用模型 在java中解码json我使用GSON 一般来说我使用并不重要
  • 如何解码 .dxf 文件?

    我想将 dxf 文件内的绘图转换为 g 代码 有一些工具可以做到这一点 但我想自己编写代码 因此 第一部分是解码 dxf 格式 然而 dxf 文件的内容看起来并不容易破译 我下载了一个 dxf 文件here并在文本编辑器中打开它 我也指的是
  • Swift:将不受约束的泛型类型转换为确认可解码的泛型类型

    情况 我有两个通用类 它们将从 api 和数据库获取数据 分别说 APIDataSource 和 DBDataSource 创建视图模型时 我将在视图模型中注入两个类中的任何一个 视图模型将使用该类来获取所需的数据 我希望视图模型与这两个类
  • 在 python 中将某些字符串(utf-8 或其他)转换为简单的 ASCII 字符串的简单方法是什么

    在我的 python 脚本中 我从一个我没有编写的函数中返回了一些字符串 它的编码各不相同 我需要将其转换为 ascii 格式 有没有一些万无一失的方法可以做到这一点 我不介意用空格或其他字符替换非 ASCII 字符 如果您想要一个明确代表
  • Swift 3 base64 解码返回 nil

    尽管我在互联网上找到了所有方法 但它可以通过在线翻译器发挥作用 比如我尝试过的一个方法 if let decodedData NSData base64Encoded parts 1 as String options NSData Bas
  • 如何在CTR模式下寻找并解密部分码流?

    我对 cryptopp 中的部分解码有疑问 使用 AES 256 CTR 编码源 CTR Mode lt AES gt Encryption e e SetKeyWithIV key 32 iv string encrypt string
  • 有没有比 Html.fromHtml() 更快的方法将 html 字符解码为字符串?

    我正在使用 Html fromHtml STRING toString 将可能包含或不包含 html 和 或 html 实体的字符串转换为纯文本字符串 这相当慢 我想我最后的计算是平均花费了大约 22 毫秒 对于大量的这些 它可以在一分钟内
  • wav <> mp3 for flash(as3)

    我想知道 MP3 解码 编码 我希望使用 AS3 在 Flash 中实现这一点 我确信这将是一个正确的痛苦 我不知道从哪里开始 有人可以提供任何指示吗 参考资料 很久以后 非常感谢大家的意见 看来我还有很长的路要走 理论上 您也可以将其作为
  • Python JSONDecoder 自定义转译null类型

    在 python 中 JSONDecoder 默认将 null 转换为 None 如下所示 我怎样才能将 null gt None 的翻译更改为不同的内容 即 null gt 猫 class json JSONDecoder encodin
  • 读取文本文件的行并收到 Charmap 解码错误

    我使用 python3 3 和 sqlite3 数据库 我有一个大约 270mb 的大文本文件 我可以在 Windows7 中使用写字板打开它 该文件中的每一行如下所示 术语 t编号 n 我想读取每一行并将值保存在数据库中 我的代码如下所示
  • 在 PHP 中将所有 HTML 特殊字符转换为 UTF-8?

    有人可以帮助我吗 如何将所有 HTML 特殊字符转换为 UTF 8 例子 Hello nbsp Word P amp H 转换成 Hello Word P H Use html entity decode http www php net
  • 如何在 Swift 5 中解码像“\xc3\xa6”这样的 utf8 文字?

    我正在从蓝牙特性中获取 WiFi SSID 列表 每个 SSID 都表示为一个字符串 有些具有 UTF8 文字 例如 xc3 xa6 我尝试了多种方法来解码这个像 let s xc3 xa6 let dec s utf8 由此我期望 pri
  • jQuery 1.4.1 中缺少 JSON stringify?

    显然 jQuery 能够将给定的对象或字符串解码为 JSON 对象 但是 我有一个 JS 对象 需要将其 POST 回服务器 并且我在 jQuery 中找不到包装 JSON stringify 函数的实用程序 Chrome Safari 4
  • 文件包含\u00c2\u00a0,转换为字符

    我有一个 JSON 文件 其中包含这样的文本 wax and voila u00c2 u00a0At the moment you can t use our 我的简单问题是如何将这些 u 代码转换 而不是删除 为空格 撇号等 Input
  • IllegalArgumentException Base64到图像解码android

    我想将 Base64 格式的 Web 服务中的图像解码为位图 并在我的 Android 应用程序中使用它 这是我的方法 public Bitmap getCaptcha throws IOException List

随机推荐