91. 解码方法(中等)
题目
一条由大写字母 A–Z 组成的消息,被逐字符编码成数字:1 表示 A,2 表示 B,……,26 表示 Z。现在给你一个只含数字的字符串 s,你需要把它切分成若干段,每段必须落在 1 到 26 之间(所以两位码只有 10–26,06 不是合法编码)。请计算共有多少种不同的切分方法;如果整串无法合法解码,返回 0。
示例 1
输入:s = "121"
输出:3
解释:可切为 "1,2,1"(ABA)、"12,1"(LA)、"1,21"(AU)。
示例 2
输入:s = "210"
输出:1
解释:只能切为 "2,10"(BJ);"21,0" 与 "2,1,0" 都不合法,因为 0 不能单独成码,而 21 后面只剩一个 0。
示例 3
输入:s = "301"
输出:0
解释:首位 3 单独成码后剩下 "01",0 不能作为一段的开头;"30" 又超过 26,因此无合法方案。
约束
1 ≤ s.length ≤ 100;s 只包含数字字符,可能以 0 开头;答案保证在 32 位有符号整数范围内。
思路
一句话本质:从第一个数字走到末尾,每步可选「走 1 个字符」或「走 2 个字符」,但每步落下的片段必须落在合法码 1–26 上,这是一个带合法性判断的路径计数问题。
最直白的做法:在每个位置做选择——要么把当前字符单独切开,要么把它和后一个字符一起切开;遇到不合法的片段就回溯。最坏情况下每个位置都有两种选择,搜索树接近满二叉树,时间复杂度为 O(2n)。而 n 最大可达 100,这个规模完全无法承受。
关键观察:假设我们已经知道「前 i-1 个字符」的方案数 dp[i-1] 和「前 i-2 个字符」的方案数 dp[i-2],那么看当前前缀 s[0..i-1]:
- 若最后一位 s[i-1] 落在 1–9,它可单独成码,方案来自 dp[i-1];
- 若最后两位 s[i-2..i-1] 落在 10–26,它们可一起成码,方案来自 dp[i-2]。
两种来源互不重叠,直接相加即可。而 dp[i] 只依赖 dp[i-1] 与 dp[i-2] 两个前状态,重叠子问题非常明显,因此可以线性扫一遍。
| 做法 | 时间复杂度 | 空间 | 问题 |
|---|---|---|---|
| 暴力回溯 | O(2n) | O(n) | 同一前缀被反复计算 |
| 记忆化搜索 | O(n) | O(n) | 可行,但需要递归栈 |
| 自底向上 DP | O(n) | O(n),可优化到 O(1) | 最直接,推荐 |
选定做法:自底向上 DP,令 dp[i] 表示前 i 个字符的合法解码方案数,按上述转移从 i=2 推到 i=n。
解法
按四步实现:
- 定义
dp[i]为s前 i 个字符的合法解码方案数。 - 初始化
dp[0] = 1(空串只有一种空方案);若s[0] == '0',直接返回 0,否则dp[1] = 1。 - 从 i=2 到 n 转移:单字符 1–9 合法则加
dp[i-1];双字符 10–26 合法则加dp[i-2]。 - 返回
dp[n]。
以 s = "121" 走一遍:
| i | 末尾单字符 | 末尾双字符 | dp[i] |
|---|---|---|---|
| 1 | ‘1’ 合法,取 dp[0] | — | 1 |
| 2 | ‘2’ 合法,加 dp[1] | “12”本篇为付费内容您已阅读试读部分,购买后即可解锁全文。 ⚠️ 本站内容已做唯一性标记并受版权保护,未经授权禁止转载,侵权必究。 |
还没有评论,来说两句?