【LeetCode】91. 解码方法 - 教程

news/2026/1/25 17:53:37/文章来源:https://www.cnblogs.com/ljbguanli/p/19530208

【LeetCode】91. 解码方法 - 教程

文章目录

  • 91. 解码方法
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 提示:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 关键难点分析
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(动态规划)
        • 状态转移流程
        • 解码过程可视化
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:动态规划(最优解法)
        • 技巧2:滚动数组优化
        • 技巧3:递归+记忆化
        • 技巧4:迭代DP(简化版)
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 简单情况
        • 特殊情况
        • 边界情况
        • 复杂情况
      • 常见错误与陷阱
        • 错误1:前导零处理错误
        • 错误2:双字符编码验证错误
        • 错误3:边界条件处理错误
      • 实战技巧总结
      • 进阶扩展
        • 扩展1:返回所有解码方案
        • 扩展2:解码特定位置
        • 扩展3:支持更多编码
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

91. 解码方法

题目描述

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

“1” -> ‘A’

“2” -> ‘B’

“25” -> ‘Y’

“26” -> ‘Z’

然而,在 解码 已编码的消息时,你意识到有许多不同的方式来解码,因为有些编码被包含在其它编码当中(“2” 和 “5” 与 “25”)。

例如,“11106” 可以映射为:

“AAJF” ,将消息分组为 (1, 1, 10, 6)
“KJF” ,将消息分组为 (11, 10, 6)
消息不能分组为 (1, 11, 06) ,因为 “06” 不是一个合法编码(只有 “6” 是合法的)。
注意,可能存在无法解码的字符串。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。如果没有合法的方式解码整个字符串,返回 0。

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = “12”
输出:2
解释:它可以解码为 “AB”(1 2)或者 “L”(12)。

示例 2:

输入:s = “226”
输出:3
解释:它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。

示例 3:

输入:s = “06”
输出:0
解释:“06” 无法映射到 “F” ,因为存在前导零(“6” 和 “06” 并不等价)。

提示:

解题思路

问题深度分析

这是经典的动态规划问题,也是字符串解码的经典应用。核心在于状态转移,在O(n)时间内计算所有可能的解码方法数。

问题本质

给定只含数字的字符串s,计算解码方法的总数。每个数字1-9对应A-I,10-26对应J-Z。

核心思想

动态规划 + 状态转移

  1. 状态定义:dp[i]表示前i个字符的解码方法数
  2. 状态转移:考虑单字符和双字符两种解码方式
  3. 边界处理:处理前导零和无效编码
  4. 优化技巧:滚动数组优化空间复杂度

关键技巧

关键难点分析

难点1:状态转移的理解

  • 需要理解单字符和双字符两种解码方式
  • 状态转移方程的正确推导
  • 边界条件的处理

难点2:前导零的处理

  • '0’不能单独解码
  • ‘06’、'00’等无效编码的处理
  • 边界情况的判断

难点3:双字符编码的验证

  • 需要验证s[i-1:i+1]是否在10-26范围内
  • 处理’0’开头的双字符编码
  • 避免越界访问
典型情况分析

情况1:一般情况

s = "226"
dp[0] = 1 (空字符串)
dp[1] = 1 (2 -> B)
dp[2] = dp[1] + dp[0] = 1 + 1 = 2 (2,2 或 22)
dp[3] = dp[2] + dp[1] = 2 + 1 = 3 (2,2,6 或 2,26 或 22,6)
结果: 3

情况2:包含0

s = "06"
dp[0] = 1
dp[1] = 0 (0不能单独解码)
dp[2] = 0 (06不能解码)
结果: 0

情况3:单字符

s = "12"
dp[0] = 1
dp[1] = 1 (1 -> A)
dp[2] = dp[1] + dp[0] = 1 + 1 = 2 (1,2 或 12)
结果: 2

情况4:全1

s = "111"
dp[0] = 1
dp[1] = 1 (1 -> A)
dp[2] = dp[1] + dp[0] = 1 + 1 = 2 (1,1 或 11)
dp[3] = dp[2] + dp[1] = 2 + 1 = 3 (1,1,1 或 1,11 或 11,1)
结果: 3
算法对比
算法时间复杂度空间复杂度特点
动态规划O(n)O(n)最优解法
滚动数组DPO(n)O(1)空间优化
递归+记忆化O(n)O(n)逻辑清晰
迭代DPO(n)O(n)避免递归

注:n为字符串长度

算法流程图

主算法流程(动态规划)
开始: 字符串s
初始化DP数组
处理边界情况
遍历字符串
单字符解码
双字符解码
更新DP状态
继续下一个字符
返回结果
状态转移流程
graph TDA[当前位置i] --> B{s[i] == '0'?}B -->|是| C[跳过单字符解码]B -->|否| D[单字符解码: dp[i] += dp[i-1]]C --> E{i >= 1?}D --> EE -->|是| F{双字符有效?}E -->|否| G[结束]F -->|是| H[双字符解码: dp[i] += dp[i-2]]F -->|否| I[跳过双字符解码]H --> GI --> G
解码过程可视化
DP状态
解码方式
字符串示例
dp[0] = 1
dp[1] = 1
dp[2] = 2
dp[3] = 3
2,2,6 -> BBF
2,26 -> BZ
22,6 -> VF
226

复杂度分析

时间复杂度详解

动态规划算法:O(n)

递归算法:O(n)

空间复杂度详解

动态规划算法:O(n)

  • DP数组长度为n+1
  • 总空间:O(n)

滚动数组优化:O(1)

  • 只使用两个变量存储状态
  • 总空间:O(1)

关键优化技巧

技巧1:动态规划(最优解法)
func numDecodings(s string) int {
n := len(s)
if n == 0 || s[0] == '0' {
return 0
}
dp := make([]int, n+1)
dp[0] = 1
dp[1] = 1
for i := 2; i <= n; i++ {
// 单字符解码
if s[i-1] != '0' {
dp[i] += dp[i-1]
}
// 双字符解码
if s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6') {
dp[i] += dp[i-2]
}
}
return dp[n]
}

优势

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
  • 逻辑清晰,易于理解
技巧2:滚动数组优化
func numDecodings(s string) int {
n := len(s)
if n == 0 || s[0] == '0' {
return 0
}
prev2 := 1 // dp[i-2]
prev1 := 1 // dp[i-1]
for i := 2; i <= n; i++ {
curr := 0
// 单字符解码
if s[i-1] != '0' {
curr += prev1
}
// 双字符解码
if s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6') {
curr += prev2
}
prev2 = prev1
prev1 = curr
}
return prev1
}

特点:使用滚动数组,空间复杂度O(1)

技巧3:递归+记忆化
func numDecodings(s string) int {
memo := make(map[int]int)
return helper(s, 0, memo)
}
func helper(s string, index int, memo map[int]int) int {
if index == len(s) {
return 1
}
if s[index] == '0' {
return 0
}
if val, ok := memo[index]; ok {
return val
}
result := helper(s, index+1, memo)
if index+1 < len(s) {
twoDigit := int(s[index]-'0')*10 + int(s[index+1]-'0')
if twoDigit <= 26 {
result += helper(s, index+2, memo)
}
}
memo[index] = result
return result
}

特点:使用递归DFS,记忆化避免重复计算

技巧4:迭代DP(简化版)
func numDecodings(s string) int {
n := len(s)
if n == 0 || s[0] == '0' {
return 0
}
dp := make([]int, n+1)
dp[0] = 1
dp[1] = 1
for i := 2; i <= n; i++ {
dp[i] = 0
// 单字符解码
if s[i-1] != '0' {
dp[i] += dp[i-1]
}
// 双字符解码
if s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6') {
dp[i] += dp[i-2]
}
}
return dp[n]
}

特点:使用迭代方法,避免递归

边界情况处理

  1. 空字符串:返回0
  2. 前导零:s[0] == '0’时返回0
  3. 单字符:直接判断是否为’0’
  4. 无效编码:超出26范围的双字符编码

测试用例设计

基础测试
输入: s = "226"
输出: 3
说明: 一般情况
简单情况
输入: s = "12"
输出: 2
说明: 单字符和双字符解码
特殊情况
输入: s = "06"
输出: 0
说明: 前导零
边界情况
输入: s = "0"
输出: 0
说明: 单字符0
复杂情况
输入: s = "11106"
输出: 2
说明: 包含0的复杂情况

常见错误与陷阱

错误1:前导零处理错误
// ❌ 错误:没有处理前导零
func numDecodings(s string) int {
dp := make([]int, len(s)+1)
dp[0] = 1
// 直接开始DP,没有检查s[0] == '0'
}
// ✅ 正确:处理前导零
func numDecodings(s string) int {
if len(s) == 0 || s[0] == '0' {
return 0
}
// 然后开始DP
}
错误2:双字符编码验证错误
// ❌ 错误:没有验证双字符编码
if s[i-2] == '1' || s[i-2] == '2' {
dp[i] += dp[i-2]
}
// ✅ 正确:验证双字符编码
if s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6') {
dp[i] += dp[i-2]
}
错误3:边界条件处理错误
// ❌ 错误:没有处理边界条件
for i := 1; i <= n; i++ {
// 直接访问s[i-1],可能越界
}
// ✅ 正确:处理边界条件
for i := 2; i <= n; i++ {
// 确保i-2 >= 0
}

实战技巧总结

  1. 状态定义:dp[i]表示前i个字符的解码方法数
  2. 状态转移:单字符和双字符两种方式
  3. 边界处理:前导零和无效编码
  4. 空间优化:滚动数组优化
  5. 状态管理:正确维护DP状态

进阶扩展

扩展1:返回所有解码方案
func getAllDecodings(s string) []string {
// 返回所有可能的解码字符串
// ...
}
扩展2:解码特定位置
func decodeAtPosition(s string, pos int) int {
// 返回解码到位置pos的方法数
// ...
}
扩展3:支持更多编码
func numDecodingsExtended(s string, maxCode int) int {
// 支持1-maxCode的编码范围
// ...
}

应用场景

  1. 密码学:消息解码和加密
  2. 通信协议:数据编码传输
  3. 算法竞赛:动态规划经典应用
  4. 系统设计:错误恢复机制
  5. 数据处理:字符串解析

代码实现

本题提供了四种不同的解法,重点掌握动态规划算法。

测试结果

测试用例动态规划滚动数组DP递归+记忆化迭代DP
基础测试
简单情况
特殊情况
边界情况

核心收获

  1. 动态规划:字符串解码的经典应用
  2. 状态转移:单字符和双字符解码
  3. 边界处理:前导零和无效编码
  4. 空间优化:滚动数组技巧
  5. 状态管理:正确维护DP状态

应用拓展

  • 动态规划基础
  • 字符串处理技术
  • 状态转移设计
  • 边界条件处理
  • 算法优化技巧

完整题解代码

package main
import (
"fmt"
)
// =========================== 方法一:动态规划(最优解法) ===========================
func numDecodings(s string) int {
n := len(s)
if n == 0 || s[0] == '0' {
return 0
}
dp := make([]int, n+1)
dp[0] = 1
dp[1] = 1
for i := 2; i <= n; i++ {
// 单字符解码
if s[i-1] != '0' {
dp[i] += dp[i-1]
}
// 双字符解码
if s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6') {
dp[i] += dp[i-2]
}
}
return dp[n]
}
// =========================== 方法二:滚动数组优化 ===========================
func numDecodings2(s string) int {
n := len(s)
if n == 0 || s[0] == '0' {
return 0
}
prev2 := 1 // dp[i-2]
prev1 := 1 // dp[i-1]
for i := 2; i <= n; i++ {
curr := 0
// 单字符解码
if s[i-1] != '0' {
curr += prev1
}
// 双字符解码
if s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6') {
curr += prev2
}
prev2 = prev1
prev1 = curr
}
return prev1
}
// =========================== 方法三:递归+记忆化 ===========================
func numDecodings3(s string) int {
if len(s) == 0 {
return 0
}
memo := make(map[int]int)
return helper(s, 0, memo)
}
func helper(s string, index int, memo map[int]int) int {
if index == len(s) {
return 1
}
if s[index] == '0' {
return 0
}
if val, ok := memo[index]; ok {
return val
}
result := helper(s, index+1, memo)
if index+1 < len(s) {
twoDigit := int(s[index]-'0')*10 + int(s[index+1]-'0')
if twoDigit <= 26 {
result += helper(s, index+2, memo)
}
}
memo[index] = result
return result
}
// =========================== 方法四:迭代DP(简化版) ===========================
func numDecodings4(s string) int {
n := len(s)
if n == 0 || s[0] == '0' {
return 0
}
dp := make([]int, n+1)
dp[0] = 1
dp[1] = 1
for i := 2; i <= n; i++ {
dp[i] = 0
// 单字符解码
if s[i-1] != '0' {
dp[i] += dp[i-1]
}
// 双字符解码
if s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6') {
dp[i] += dp[i-2]
}
}
return dp[n]
}
// =========================== 测试代码 ===========================
func main() {
fmt.Println("=== LeetCode 91: 解码方法 ===\n")
testCases := []struct {
name     string
s        string
expected int
}{
{
name:     "Test1: Basic case",
s:        "226",
expected: 3,
},
{
name:     "Test2: Simple case",
s:        "12",
expected: 2,
},
{
name:     "Test3: Leading zero",
s:        "06",
expected: 0,
},
{
name:     "Test4: Single zero",
s:        "0",
expected: 0,
},
{
name:     "Test5: Complex case",
s:        "11106",
expected: 2,
},
{
name:     "Test6: All ones",
s:        "111",
expected: 3,
},
{
name:     "Test7: Large number",
s:        "27",
expected: 1,
},
{
name:     "Test8: Empty string",
s:        "",
expected: 0,
},
{
name:     "Test9: Single digit",
s:        "1",
expected: 1,
},
{
name:     "Test10: Two zeros",
s:        "00",
expected: 0,
},
}
methods := map[string]func(string) int{
"动态规划(最优解法)": numDecodings,
"滚动数组优化":     numDecodings2,
"递归+记忆化":     numDecodings3,
"迭代DP(简化版)":  numDecodings4,
}
for name, method := range methods {
fmt.Printf("方法:%s\n", name)
passCount := 0
for i, tt := range testCases {
got := method(tt.s)
// 验证结果是否正确
valid := got == tt.expected
status := "✅"
if !valid {
status = "❌"
} else {
passCount++
}
fmt.Printf("  测试%d: %s\n", i+1, status)
if status == "❌" {
fmt.Printf("    输入: %s\n", tt.s)
fmt.Printf("    输出: %d\n", got)
fmt.Printf("    期望: %d\n", tt.expected)
}
}
fmt.Printf("  通过: %d/%d\n\n", passCount, len(testCases))
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1215319.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2026 主流GEO服务商全景图谱,企业GEO服务商选型指南

《2026年To B企业AI获客必看:GEO机制深度解析与服务商权威选型指南》 随着生成式AI全面渗透用户信息获取路径,GEO(Generative Engine Optimization,生成引擎优化)已成为To B企业不可忽视的新增长引擎。据艾瑞咨询…

三相与两相步进方案的矢量控制及超前角控制:内置微控制器的技术解析

三相&#xff0c;两相步进方案&#xff0c;矢量控制&#xff0c;超前角控制&#xff0c;内置微控制器&#xff01; 最近在搞步进电机控制方案选型&#xff0c;发现三相和两相系统的选择特别有意思。两种方案看似差不多&#xff0c;实际调起来完全是两个世界。今天咱们直接上干…

光伏储能交直流微电网matlab/simulink仿真,风光储能联合发电系统simulink仿...

光伏储能交直流微电网matlab/simulink仿真&#xff0c;风光储能联合发电系统simulink仿真&#xff0c;光伏风电储能能量管理&#xff0c;光伏风电混合发电系统储能系统并网最近在搞微电网仿真的时候&#xff0c;发现风光储联合系统的参数协调真是让人头秃。特别是当光伏阵列和双…

双亲表示法构造树-----Java实现

package Data_Structure.Tree; import java.io.IOException; import java.util.NoSuchElementException; import java.util.Scanner; //双亲表示法构造树,该树使用层序进行构造,通过parent下标索引双亲结点 public c…

KiCad V10新特性前瞻

https://forum.kicad.info/t/post-v9-new-features-and-development-news/58848 KiCad采用年度发布周期&#xff0c;主版本通常在每年2月至3月间发布。虽然正式版尚未推出&#xff0c;但由于KiCad始终秉持开源理念&#xff08;这恰是其最大优势&#xff09;&#xff0c;开发者…

基于传统材料力学势能法的健康齿轮时变啮合刚度数值分析

一、核心结论 传统材料力学势能法是计算健康齿轮时变啮合刚度的经典方法&#xff0c;通过将轮齿简化为变截面悬臂梁&#xff0c;考虑弯曲、剪切、轴向压缩、赫兹接触及基体变形等能量分量&#xff0c;能够准确反映齿轮啮合过程中的刚度变化。该方法具有计算效率高、精度满足工…

电气设计的隐藏外挂:1:1元器件图库实战

电气高低压成套元器件 几乎每个厂家的元器件都画出来了尺寸大小电气高低压成套元器件 几乎每个厂家的元器件都画出来了尺寸大小型号都有&#xff0c;应有尽有&#xff0c;全部1:1尺寸&#xff0c;直接标注就OK配电柜设计现场最要命的场景&#xff1a;左手抓着某厂家的断路器样本…

Product Hunt 每日热榜 | 2026-01-25

1. Humans in the Loop 标语&#xff1a;一个免费的社区&#xff0c;专门讨论与代理编程与人工智能相关的所有事。 介绍&#xff1a;“有人的参与”是一个为热爱利用人工智能加速工作的伙伴们打造的地方。这里是一个免费的社区&#xff0c;大家可以在这里分享关于Claude Code…

构建 OpenHarmony 跨设备任务协同中心:Flutter 实现多端任务流转与状态同步

一、引言&#xff1a;从单设备到分布式协同 OpenHarmony 的核心愿景之一是 “超级终端” —— 多个物理设备无缝协同&#xff0c;形成一个逻辑上的统一工作空间。例如&#xff1a; 在手机上开始编辑文档&#xff0c;走到平板前自动续写&#xff1b;车机导航途中&#xff0c;到家…

构建 OpenHarmony 智能场景自动化配置面板:Flutter 实现可视化规则编排

一、引言&#xff1a;从手动操作到场景自动化 在 OpenHarmony 驱动的全场景智慧生态中&#xff0c;用户不再满足于“打开 App 控制设备”&#xff0c;而是期望系统能主动理解意图、自动执行操作。例如&#xff1a; “如果我到家&#xff0c;就打开客厅灯和空调”&#xff1b;…

Simulink双Y-30度六相感应电机模型,matlab18B版本。 六相交流供电

Simulink双Y-30度六相感应电机模型&#xff0c;matlab18B版本。 六相交流供电&#xff0c;做六相电机容错、多自由度控制以及本体设计的同学能用得上&#xff0c;六相感应电机模型电机工作原理参考文献等相关资料 最近在实验室折腾六相感应电机控制&#xff0c;发现Matlab 201…

强烈安利8个一键生成论文工具,继续教育学生论文写作必备!

强烈安利8个一键生成论文工具&#xff0c;继续教育学生论文写作必备&#xff01; AI 工具如何助力论文写作&#xff0c;提升效率与质量 在当前的学术环境中&#xff0c;AI 工具已经成为越来越多继续教育学生的重要助手。尤其是在论文写作过程中&#xff0c;这些工具不仅能够显…

ubuntu_server安装教程

准备文件: ubuntu-live-server-amd64.iso ventoy-1.1.10-windows.zip系统启动盘制作 网盘下载: https://pan.quark.cn/s/a804ae8dd78f 准备4G以上U盘,会清空u盘数据,尽量备份数据 1.不能用rufus制作系统启动盘,否…

基于深度学习的 pcb 缺陷检测系统

目录 深度学习在PCB缺陷检测中的应用典型系统架构设计关键技术实现要点性能优化方向工业部署注意事项 源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 深度学习在PCB缺陷检测中的应用 深度学习技术在PCB缺陷检测中展现出显著优势&…

2025年市面上热门的自动化立体库制造企业怎么选,轻型货架/隔板货架/仓储货架/中型货架,自动化立体库供应厂家哪家强

行业洞察:自动化立体库的“选型战”如何破局? 随着制造业智能化转型加速,自动化立体库已成为企业提升仓储效率、降低运营成本的核心基础设施。据统计,2024年国内自动化立体库市场规模突破320亿元,年复合增长率达1…

基于单片机的汽车倒车雷达超声波测距系统设计

目录系统概述硬件设计软件设计工作流程优化方向源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;系统概述 基于单片机的汽车倒车雷达超声波测距系统通过超声波传感器发射和接收信号&#xff0c;结合单片机处理数据并计算距离&#xff0c…

JWT 解码工具

JWT 解码工具页面index.html脚本运行效果如下&#xff1a;页面index.html <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.…

基于深度学习的电动车头盔检测系统

目录 电动车头盔检测系统的背景与需求核心技术框架关键实现步骤性能优化方向典型应用场景挑战与解决方案 源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 电动车头盔检测系统的背景与需求 电动车头盔检测系统通过计算机视觉技术自动识…

keycloak测试11.0.2 for windows

keycloak测试11.0.2PasswordClient credentialsIntrospectionAuthorization codePassword 应用于后端处理: set KEYCLOAK_HOSThttp://keycloak.demofor.com.cn/ set KEYCLOAK_REALMkeycloak-learn set CLIENT_IDchapter-1 set CLIENT_SECRET149240f3-31c6-46dc-bab4-0b53c0ee1…

基于深度学习的番茄检测系统

目录 深度学习番茄检测系统概述核心功能与技术系统架构示例性能优化方向应用案例 源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 深度学习番茄检测系统概述 深度学习番茄检测系统利用计算机视觉和深度学习技术&#xff0c;自动识别、…