ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LeetCode 91题解码方式:动态规划解法详解

LeetCode 91题解码方式:动态规划解法详解

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个字符的解码方式数量。状态转移需要考虑以下情况:

  1. 当前字符单独解码(必须是非零数字)
  2. 当前字符与前一个字符组合解码(必须在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 curr

3.2 关键代码解析

  1. 初始检查:空字符串或首字符为'0'直接返回0
  2. 变量初始化:prev代表dp[i-2],curr代表dp[i-1]
  3. 循环处理:
    • single:当前字符单独解码的有效性
    • double:与前字符组合解码的有效性
  4. 状态更新:滚动更新prev和curr的值

提示:使用字符串切片s[i-1:i+1]时要注意Python的切片是左闭右开区间,所以实际取的是s[i-1]和s[i]两个字符。

4. 常见错误与调试技巧

4.1 典型错误案例

  1. 忽略前导零:如输入"01"应返回0
  2. 连续零处理:如输入"100"应返回0
  3. 边界条件错误:空字符串应返回1(作为初始条件)
  4. 数字组合越界:如"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 变种问题思考

  1. 输出所有解码方式:改为回溯问题,需要记录路径
  2. 包含通配符'*':如LeetCode 639题
  3. 最大解码价值:每个字母有不同价值,求最大总价值

6. 复杂度分析与实际性能

时间复杂度:O(n),只需一次遍历字符串 空间复杂度:O(1),只使用了常数个额外变量

在实际测试中,这个解法可以轻松处理长度超过10^5的字符串。我在LeetCode提交记录中最好的运行时间是28ms,超过了98%的Python提交。

7. 刷题心得与进阶建议

这道题教会我动态规划问题中边界条件的重要性。几个关键收获:

  1. 空字符串的处理方式很特殊
  2. 前导零需要立即返回0
  3. 数字组合必须在10-26之间

对于想进一步练习动态规划的同学,我推荐按照这个顺序刷题:

    1. Climbing Stairs
    1. House Robber
  1. 这道Decode Ways
    1. Word Break
    1. Coin Change

在实际面试中,这类问题通常会要求先写出递归解法,再优化为DP,最后进行空间优化。建议练习时也按照这个流程来。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进