优化解码方法:记忆化搜索和空间优化动态规划的实用指南 【LeetCode 题目 91】

简介: 优化解码方法:记忆化搜索和空间优化动态规划的实用指南 【LeetCode 题目 91】

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

python源码解读

程序员必备的数学知识与应用

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

本文探讨了解码方法的多种算法,包括动态规划、记忆化回溯、和位操作策略,比较了它们在处理编码问题时的效率和适用性,为解决类似问题提供参考。


题目描述

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

'A' -> 1
'B' -> 2
...
'Z' -> 26

给定一个只包含数字的非空字符串,请计算解码方法的总数。

输入格式
  • s:一个只包含数字的字符串。
输出格式
  • 返回解码可能的总数。

示例

示例 1
输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。

方法一:动态规划

解题步骤
  1. 定义状态dp[i] 表示到字符串 s 的第 i 个字符为止的解码方法数。
  2. 状态转移
  • 如果 s[i] 是有效数字(1-9),则 dp[i] += dp[i-1]
  • 如果两位数 s[i-1:i+1] 是有效编码(10-26),则 dp[i] += dp[i-2]
完整的规范代码
def numDecodings(s):
    """
    动态规划解决解码方法计数问题
    :param s: str, 输入的数字字符串
    :return: int, 解码方法的总数
    """
    if not s or s[0] == '0':
        return 0
    dp = [0] * (len(s) + 1)
    dp[0], dp[1] = 1, 1
    for i in range(2, len(s) + 1):
        if s[i-1] != '0':
            dp[i] += dp[i-1]
        if 10 <= int(s[i-2:i]) <= 26:
            dp[i] += dp[i-2]
    return dp[-1]
# 示例调用
print(numDecodings("12"))  # 输出: 2
算法分析
  • 时间复杂度:(O(n)),遍历一次字符串。
  • 空间复杂度:(O(n)),用于存储状态的数组。

方法二:空间优化的动态规划

解题步骤
  1. 空间优化:使用两个变量滚动更新,减少空间使用。
完整的规范代码
def numDecodings(s):
    """
    空间优化的动态规划解决解码方法计数问题
    :param s: str, 输入的数字字符串
    :return: int, 解码方法的总数
    """
    if not s or s[0] == '0':
        return 0
    prev, curr = 1, 1  # dp[-1] 和 dp[0]
    for i in range(1, len(s)):
        tmp = curr
        if s[i] == '0':
            if s[i-1] not in ('1', '2'):
                return 0
            curr = prev
        elif s[i-1] == '1' or (s[i-1] == '2' and s[i] <= '6'):
            curr += prev
        prev = tmp
    return curr
# 示例调用
print(numDecodings("12"))  # 输出: 2
算法分析
  • 时间复杂度:(O(n)),单次遍历。
  • 空间复杂度:(O(1)),只使用常数级别的额外空间。

方法三:回溯法

解题步骤
  1. 尝试每种可能:从第一个字符开始,尝试将字符串分割成可能的数字,如果有效则继续递归解码剩余部分。
  2. 处理边界和无效情况:确保不解析像 ‘06’ 这样的数字。
完整的规范代码
def numDecodings(s):
    """
    使用回溯法解决解码方法计数问题
    :param s: str, 输入的数字字符串
    :return: int, 解码方法的总数
    """
    def backtrack(index):
        if index == len(s):
            return 1
        if s[index] == '0':
            return 0
        if index == len(s) - 1:
            return 1
        
        ans = backtrack(index + 1)
        if int(s[index:index+2]) <= 26:
            ans += backtrack(index + 2)
        return ans
    
    return backtrack(0)
# 示例调用
print(numDecodings("226"))  # 输出: 3
算法分析
  • 时间复杂度:最坏情况下 (O(2^n)),因为每个位置可能有两种选择。
  • 空间复杂度:(O(n)),递归的深度可能达到字符串的长度。

方法四:带记忆化的回溯法

解题步骤
  1. 记忆化存储:使用一个数组或哈希表来存储已经计算过的解码方法数,避免重复计算。
  2. 递归解码:使用递归函数尝试每种可能的分割方式,如果当前位置已经计算过,直接返回存储的结果。
完整的规范代码
def numDecodings(s):
    """
    带记忆化的回溯法解决解码方法计数问题
    :param s: str, 输入的数字字符串
    :return: int, 解码方法的总数
    """
    memo = {}
    
    def decode(index):
        if index == len(s):
            return 1
        if s[index] == '0':
            return 0
        if index in memo:
            return memo[index]
        if index == len(s) - 1:
            return 1
        
        answer = decode(index + 1)
        if index + 1 < len(s) and (s[index] == '1' or (s[index] == '2' and s[index + 1] <= '6')):
            answer += decode(index + 2)
        
        memo[index] = answer
        return answer
    return decode(0)
# 示例调用
print(numDecodings("226"))  # 输出: 3
算法分析
  • 时间复杂度:(O(n)),每个字符位置最多被解码一次。
  • 空间复杂度:(O(n)),递归栈和记忆化存储使用的空间。

方法五:Fibonacci 类似的方法

解题步骤
  1. 动态规划初始化:使用类似 Fibonacci 序列的方式,初始化两个变量来保存前两个状态的解码方法数。
  2. 迭代更新:对于每个位置,根据前一或两个字符是否能有效解码更新当前的解码方法数。
完整的规范代码
def numDecodings(s):
    """
    Fibonacci 类似的方法解决解码方法计数问题
    :param s: str, 输入的数字字符串
    :return: int, 解码方法的总数
    """
    if not s or s[0] == '0':
        return 0
    
    prev, curr = 1, 1  # dp[-1] 和 dp[0]
    for i in range(1, len(s)):
        temp = curr
        if s[i] == '0':
            if s[i-1] not in ('1', '2'):
                return 0
            curr = prev
        else:
            if i > 0 and (s[i-1] == '1' or (s[i-1] == '2' and s[i] <= '6')):
                curr += prev
        prev = temp
    return curr
# 示例调用
print(numDecodings("12"))  # 输出: 2
算法分析
  • 时间复杂度:(O(n)),一次遍历字符串。
  • 空间复杂度:(O(1)),使用常量空间进行迭代。

不同算法的优劣势对比

特征 方法一:动态规划 方法二:空间优化的动态规划 方法三:回溯法 方法四:记忆化回溯 方法五:Fibonacci 方法
时间复杂度 (O(n)) (O(n)) (O(2^n)) (O(n)) (O(n))
空间复杂度 (O(n)) (O(1)) (O(n)) (O(n)) (O(1))
优势 易于理解和实现 优化空间复杂度 直观,容易理解 减少重复计算 空间效率高
劣势 空间使用较高 相较于方法一稍复杂 可能非常慢,尤其是对于大输入 空间复杂度较高 需要处理多种边界条件

应用示例

这些算法在编码解码、数据压缩、信号处理等领域有广泛应用,尤其适合处理那些需要快速从序列化数据中恢复信息的场景。在处理通信数据、恢复加密信息等领域,有效的解码算法是必不可少的。


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
15小时前
|
C语言
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
6 1
|
11天前
|
算法 Python
LeetCode 常用方法
LeetCode 常用方法
|
12天前
|
算法
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
|
12天前
|
容器
【LeetCode刷题】栈和队列题目练习~
【LeetCode刷题】栈和队列题目练习~
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(4)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
12天前
|
存储 算法
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(1)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
12天前
|
算法
【经典LeetCode算法题目专栏分类】【第11期】递归问题:字母大小写全排列、括号生成
【经典LeetCode算法题目专栏分类】【第11期】递归问题:字母大小写全排列、括号生成
|
12天前
|
算法
【经典LeetCode算法题目专栏分类】【第9期】深度优先搜索DFS与并查集:括号生成、岛屿问题、扫雷游戏
【经典LeetCode算法题目专栏分类】【第9期】深度优先搜索DFS与并查集:括号生成、岛屿问题、扫雷游戏
|
12天前
|
算法
【经典LeetCode算法题目专栏分类】【第8期】滑动窗口:最小覆盖子串、字符串排列、找所有字母异位词、 最长无重复子串
【经典LeetCode算法题目专栏分类】【第8期】滑动窗口:最小覆盖子串、字符串排列、找所有字母异位词、 最长无重复子串
|
12天前
|
算法
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表