91. 解码方法 (decode ways)

4,061 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情

91. 解码方法 题目描述:一条包含字母 A-Z 的消息通过以下映射进行了 编码

A=>1B=>2...Z=>26A => 1、B => 2、...、Z => 26

解码 已编码的消息,所有数字必须基于该映射方法,方向映射回字母有多种方法。给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

示例1示例2
输入s=12s = “12”
输出22
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)
输入s=226s = “226”
输出33
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

中规中矩的动态规划

动态规划题目最重要的一点就是探寻 最优子结构

1、确定 dp 状态数组

定义 dp[i]dp[i]strstr[0:i)[0: i) 区间的解码方法总数,其中 i[0,n]i \in [0,n]n=str.lengthn = str.length

NOTE: 通常情况下,引入一个“哨兵”节点,会简化问题,即 dp[0]dp[0] 节点代表 strstr[0:0)[0: 0) 区间的解码方法总数,此区间并没有对应字符。

2、确定 dp 状态方程

对于 str[i]str[i] 的情况,我们分开讨论:

  • str[i]==0str[i] == “0” 时:

    • 如果 str[i1]==1”、“2str[i - 1] == “1”、“2” 时,dp[i]=dp[i2]dp[i] = dp[i - 2]。此时 dp[i]dp[i] 是以 00 结尾字符串片段的解码方法总数。这是一个特殊的位置,由于 00 不能作为编码的起始位置,只能作为编码的结束位置,且位数必须是 22 的解码对,即 J=>10J => 10T=>20T => 20。所以,如果当前 str[i]==0str[i] == “0”strstr[0:i][0: i] 的解码方法总数一定等于 strstr[0:i2][0: i-2] 的解码方法总数,因为 strstr 在区间 [i1:i][i-1:i] 只能对应 J=>10J => 10T=>20T => 20

    • 如果 str[i1]!==1”、“2str[i - 1] !== “1”、“2” 时,此时一定无法成功解码,直接返回 00 即可(可自行验证,例如 "23602","33001",都无法成功解码)。

  • str[i]!==0str[i] !== “0” 时:

    • 如果 str[i1]==1str[i - 1] == “1”,那么一定存在 dp[i]=dp[i1]+dp[i2]dp[i] = dp[i - 1] + dp[i - 2]str[i]!==0str[i] !== “0”,说明 str[i]str[i] 一定可以单独解码,此时,dp[i]=dp[i1]dp[i] = dp[i - 1];又因为 str[i1]==1str[i - 1] == “1”str[i1]str[i-1]str[i]str[i] 组合起来也可以解码,此时 dp[i]=dp[i2]dp[i] = dp[i - 2]。故两者之和才是 dp[i]dp[i] 最后的值。

    • 如果 str[i1]==2str[i - 1] == “2”str[i]==1”、“2”、“3”、“4”、“5”、“6str[i] == “1”、“2”、“3”、“4”、“5”、“6”,同理 dp[i]=dp[i1]+dp[i2]dp[i] = dp[i - 1] + dp[i - 2]

    • 如果 str[i1]!==1”、“2str[i - 1] !== “1” 、“2” 或者 str[i]==7”、“8”、“9str[i] == “7”、“8”、“9”,一定存在 dp[i]=dp[i1]dp[i] = dp[i - 1]。因为此时 str[i]str[i] 只能单独解析。

3、确定 dp 初始状态

因为状态转移方程涉及到 i2i - 2,所以至少要初始化两个节点,即

  • dp[0]dp[0]strstr[0:0)[0: 0) 区间的解码方法总数,此区间没有字符,也视为 11 种解码方法,故 dp[0]=1dp[0] = 1

  • dp[1]dp[1]strstr[0:1)[0: 1) 区间的解码方法总数,只有 str[0]str[0],解码方法总数只能为 1,故 dp[1]=1dp[1] = 1

由于 str[0]str[0] 可能为 00,所以这里要提前判断。

4、确定遍历顺序

i=2i = 2i=ni = n

5、确定最终返回值

回归动态规划的定义中:返回 dp[n]dp[n],其值为 strstr[0:n)[0: n) 区间的解码方法总数。

6、代码示例

/**
 * 空间复杂度 O(n),n是s的长度
 * 时间复杂度 O(n)
 */
function numDecodings(s: string): number {
    if (s[0] === '0') return 0;

    const n = s.length;
    const dp = new Array(n + 1).fill(0);

    dp[0] = 1;
    dp[1] = 1;

    for (let i = 2; i <= n; i++) {
        const curr = s[i - 1];
        const prev = s[i - 2];

        if (curr === '0') {
            if (prev === '1' || prev === '2') {
                dp[i] = dp[i - 2];
            } else {
                return 0;
            }
        } else {
            if (prev === '1' || (prev === '2' && curr <= '6' )) {
                dp[i] = dp[i - 1] + dp[i - 2];
            } else {
                dp[i] = dp[i - 1];
            }
        }
    }

    return dp[n];
};

参考

# 重识动态规划