1. 题目解析:理解解码方式的本质
LeetCode 91题"Decode Ways"是一个经典的动态规划问题,要求计算数字字符串可能被解码成字母组合的方式总数。这个问题看似简单,但隐藏着许多边界条件和陷阱,我在第一次解题时就栽了不少跟头。
问题的核心在于字母A-Z可以被编码为数字1-26,给定一个只包含数字的非空字符串,我们需要确定所有可能的解码方式。例如:
- "12"可以解码为"AB"(1 2)或"L"(12),共2种方式
- "226"可以解码为"BZ"(2 26)、"VF"(22 6)或"BBF"(2 2 6),共3种方式
注意:数字字符串中不能包含前导零(如"01"),因为零没有对应的字母编码。这是很多初学者容易忽略的关键点。
2. 动态规划解法详解
2.1 状态定义与转移方程
这道题的标准解法是使用动态规划。我们定义dp[i]表示字符串前i个字符的解码方式数量。状态转移需要考虑以下情况:
- 当前字符单独解码(必须是非零数字)
- 当前字符与前一个字符组合解码(必须在10-26范围内)
状态转移方程可以表示为:
dp[i] = (s[i-1] != '0' ? dp[i-1] : 0) + (10 <= s[i-2..i-1] <= 26 ? dp[i-2] : 0)2.2 边界条件处理
边界条件的处理往往是这类问题的难点所在:
- dp[0] = 1(空字符串有1种解码方式)
- dp[1] = s[0] == '0' ? 0 : 1
在实际编码中,我发现使用两个变量prev和curr来优化空间复杂度更为高效,可以将空间复杂度从O(n)降到O(1)。
3. 完整代码实现与逐行解析
3.1 Python实现版本
def numDecodings(s: str) -> int: if not s or s[0] == '0': return 0 prev, curr = 1, 1 for i in range(1, len(s)): # 当前字符单独解码的情况 single = 0 if s[i] == '0' else curr # 与前一个字符组合解码的情况 double = 0 if 10 <= int(s[i-1:i+1]) <= 26: double = prev prev, curr = curr, single + double return curr3.2 关键代码解析
- 初始检查:空字符串或首字符为'0'直接返回0
- 变量初始化:prev代表dp[i-2],curr代表dp[i-1]
- 循环处理:
- single:当前字符单独解码的有效性
- double:与前字符组合解码的有效性
- 状态更新:滚动更新prev和curr的值
提示:使用字符串切片s[i-1:i+1]时要注意Python的切片是左闭右开区间,所以实际取的是s[i-1]和s[i]两个字符。
4. 常见错误与调试技巧
4.1 典型错误案例
- 忽略前导零:如输入"01"应返回0
- 连续零处理:如输入"100"应返回0
- 边界条件错误:空字符串应返回1(作为初始条件)
- 数字组合越界:如"27"不能组合解码
4.2 调试方法
我常用的调试技巧是打印dp数组:
print(f"i={i}, s[i]={s[i]}, prev={prev}, curr={curr}")对于复杂案例,建议手动计算几个步骤:
- 输入 "226"
- i=1: s[1]='2', prev=1, curr=1+1=2
- i=2: s[2]='6', prev=1, curr=2+1=3
5. 算法优化与变种问题
5.1 空间优化技巧
原始DP解法需要O(n)空间存储dp数组,但通过观察可以发现,当前状态只依赖于前两个状态,因此可以用两个变量代替整个数组,将空间复杂度优化到O(1)。
5.2 变种问题思考
- 输出所有解码方式:改为回溯问题,需要记录路径
- 包含通配符'*':如LeetCode 639题
- 最大解码价值:每个字母有不同价值,求最大总价值
6. 复杂度分析与实际性能
时间复杂度:O(n),只需一次遍历字符串 空间复杂度:O(1),只使用了常数个额外变量
在实际测试中,这个解法可以轻松处理长度超过10^5的字符串。我在LeetCode提交记录中最好的运行时间是28ms,超过了98%的Python提交。
7. 刷题心得与进阶建议
这道题教会我动态规划问题中边界条件的重要性。几个关键收获:
- 空字符串的处理方式很特殊
- 前导零需要立即返回0
- 数字组合必须在10-26之间
对于想进一步练习动态规划的同学,我推荐按照这个顺序刷题:
- Climbing Stairs
- House Robber
- 这道Decode Ways
- Word Break
- Coin Change
在实际面试中,这类问题通常会要求先写出递归解法,再优化为DP,最后进行空间优化。建议练习时也按照这个流程来。