开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情
91. 解码方法 题目描述:一条包含字母 A-Z 的消息通过以下映射进行了 编码。
要 解码 已编码的消息,所有数字必须基于该映射方法,方向映射回字母有多种方法。给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。
| 示例1 | 示例2 |
|---|---|
| 输入: 输出: 解释: 它可以解码为 "AB"(1 2)或者 "L"(12) | 输入: 输出: 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 |
中规中矩的动态规划
动态规划题目最重要的一点就是探寻 最优子结构。
1、确定 dp 状态数组
定义 为 在 区间的解码方法总数,其中 ,。
NOTE: 通常情况下,引入一个“哨兵”节点,会简化问题,即 节点代表 在 区间的解码方法总数,此区间并没有对应字符。
2、确定 dp 状态方程
对于 的情况,我们分开讨论:
-
当 时:
-
如果 时,。此时 是以 结尾字符串片段的解码方法总数。这是一个特殊的位置,由于 不能作为编码的起始位置,只能作为编码的结束位置,且位数必须是 的解码对,即 或 。所以,如果当前 , 在 的解码方法总数一定等于 在 的解码方法总数,因为 在区间 只能对应 或 。
-
如果 时,此时一定无法成功解码,直接返回 即可(可自行验证,例如 "23602","33001",都无法成功解码)。
-
-
当 时:
-
如果 ,那么一定存在 。,说明 一定可以单独解码,此时,;又因为 , 和 组合起来也可以解码,此时 。故两者之和才是 最后的值。
-
如果 且 ,同理
-
如果 或者 ,一定存在 。因为此时 只能单独解析。
-
3、确定 dp 初始状态
因为状态转移方程涉及到 ,所以至少要初始化两个节点,即
-
为 在 区间的解码方法总数,此区间没有字符,也视为 种解码方法,故 ;
-
为 在 区间的解码方法总数,只有 ,解码方法总数只能为 1,故 。
由于 可能为 ,所以这里要提前判断。
4、确定遍历顺序
从 到 。
5、确定最终返回值
回归动态规划的定义中:返回 ,其值为 在 区间的解码方法总数。
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];
};