LeetCode 题目 87:递归\动态规划\哈希实现 扰乱字符串

本文介绍了四种不同的算法(递归、记忆化搜索、动态规划、优化动态规划及字符串哈希)来解决判断字符串s2是否为字符串s1的扰乱字符串的问题,分析了它们的时间和空间复杂度,并探讨了各自的优缺点和应用场景。
摘要由CSDN通过智能技术生成

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
LeetCode解锁1000题: 打怪升级之旅
python数据分析可视化:企业实战案例
python源码解读
程序员必备的数学知识与应用
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

题目描述

给定一个字符串 s1,我们可以通过对其进行翻转和交换子字符串的方式生成另一个字符串 s2。确定 s2 是否是 s1 的扰乱字符串。

输入格式
  • s1:原始字符串。
  • s2:可能的扰乱字符串。
输出格式
  • 返回一个布尔值,表示 s2 是否是 s1 的扰乱字符串。

示例

示例 1
输入: s1 = "great", s2 = "rgeat"
输出: true
示例 2
输入: s1 = "abcde", s2 = "caebd"
输出: false

方法一:递归

解题步骤
  1. 递归基准情况:如果 s1s2 相等,返回 true
  2. 字符验证:如果两个字符串排序后不等,返回 false
  3. 递归分割:尝试对 s1 进行分割,检查是否能通过扰乱得到 s2
完整的规范代码
def isScramble(s1, s2):
    """
    递归判断 s2 是否是 s1 的扰乱字符串
    :param s1: str, 原始字符串
    :param s2: str, 可能的扰乱字符串
    :return: bool
    """
    if s1 == s2:
        return True
    if sorted(s1) != sorted(s2):
        return False
    n = len(s1)
    for i in range(1, n):
        # 检查不交换的情况
        if isScramble(s1[:i], s2[:i]) and isScramble(s1[i:], s2[i:]):
            return True
        # 检查交换的情况
        if isScramble(s1[:i], s2[-i:]) and isScramble(s1[i:], s2[:-i]):
            return True
    return False

# 示例调用
print(isScramble("great", "rgeat"))  # 输出: true
print(isScramble("abcde", "caebd"))  # 输出: false
算法分析
  • 时间复杂度:(O(n^4)),在递归过程中需要多次分割字符串和比较。
  • 空间复杂度:(O(n)),递归深度依赖于字符串长度。

方法二:记忆化搜索

解题步骤
  1. 使用哈希表存储结果:使用字典存储已计算的结果,避免重复计算。
  2. 递归搜索:基于递归实现,但在每次递归前查询字典。
完整的规范代码
def isScramble(s1, s2, memo={}):
    """
    记忆化搜索判断 s2 是否是 s1 的扰乱字符串
    :param s1: str, 原始字符串
    :param s2: str, 可能的扰乱字符串
    :param memo: dict, 记忆化存储已计算的结果
    :return: bool
    """
    if (s1, s2) in memo:
        return memo[(s1, s2)]
    if s1 == s2:
        memo[(s1, s2)] = True
        return True
    if sorted(s1) != sorted(s2):
        memo[(s1, s2)] = False
        return False
    n = len(s1)
    for i in range(1, n):
        if (isScramble(s1[:i], s2[:i], memo) and isScramble(s1[i:], s2[i:], memo)) or \
           (isScramble(s1[:i], s2[-i:], memo) and isScramble(s1[i:], s2[:-i], memo)):
            memo[(s1, s2)] = True
            return True
    memo[(s1, s2)] = False
    return False

# 示例调用
print(isScramble("great", "rgeat"))  # 输出: true
print(isScramble("abcde", "caebd"))  # 输出: false
算法分析
  • 时间复杂度:(O(n^3)),使用记忆化后,大多数重复的递归调用被省略。
  • 空间复杂度:(O(n^2)),存储已解决问题的状态需要的空间。

方法三:动态规划

解题步骤
  1. 定义状态dp[i][j][k] 表示从 s1 的第 i 个字符开始和从 s2 的第 j 个字符开始,长度为 k 的子串是否互为扰乱字符串。
  2. 状态转移:根据字符是否相等以及可能的分割情况更新状态。
完整的规范代码
def isScramble(s1, s2):
    """
    动态规划判断 s2 是否是 s1 的扰乱字符串
    :param s1: str, 原始字符串
    :param s2: str, 可能的扰乱字符串
    :return: bool
    """
    if len(s1) != len(s2) or sorted(s1) != sorted(s2):
        return False

    n = len(s1)
   dp = [[[False] * (n + 1) for _ in range(n)] for __ in range(n)]

    # 初始化长度为1的情况
    for i in range(n):
        for j in range(n):
            dp[i][j][1] = s1[i] == s2[j]

    # 动态规划计算长度为k的情况
    for k in range(2, n + 1):  # 子串长度
        for i in range(n - k + 1):  # s1的起始位置
            for j in range(n - k + 1):  # s2的起始位置
                for l in range(1, k):  # 分割点
                    if (dp[i][j][l] and dp[i + l][j + l][k - l]) or (dp[i][j + k - l][l] and dp[i + l][j][k - l]):
                        dp[i][j][k] = True
                        break
    
    return dp[0][0][n]

# 示例调用
print(isScramble("great", "rgeat"))  # 输出: true
print(isScramble("abcde", "caebd"))  # 输出: false
算法分析
  • 时间复杂度:(O(n^4)),其中 (n) 是字符串的长度。我们需要计算所有长度、所有起始位置和所有可能的分割点的状态。
  • 空间复杂度:(O(n^3)),存储状态所需的三维数组。

方法四:优化的动态规划

解题步骤
  1. 使用二维数组:降低空间复杂度,每次只保存当前长度的状态信息。
  2. 优化状态转移:只依赖于小于当前长度的状态,因此可以使用滚动数组的技术。
完整的规范代码
def isScramble(s1, s2):
    """
    优化的动态规划算法,使用二维数组降低空间复杂度
    :param s1: str, 原始字符串
    :param s2: str, 可能的扰乱字符串
    :return: bool
    """
    if len(s1) != len(s2) or sorted(s1) != sorted(s2):
        return False

    n = len(s1)
    dp = [[[False] * n for _ in range(n)] for __ in range(2)]

    # 初始化长度为1的情况
    for i in range(n):
        for j in range(n):
            dp[1][i][j] = s1[i] == s2[j]

    # 动态规划计算长度大于1的情况
    for k in range(2, n + 1):  # 子串长度
        for i in range(n - k + 1):  # s1的起始位置
            for j in range(n - k + 1):  # s2的起始位置
                for l in range(1, k):  # 分割点
                    if (dp[(l)%2][i][j] and dp[(k-l)%2][i+l][j+l]) or (dp[(l)%2][i][j+k-l] and dp[(k-l)%2][i+l][j]):
                        dp[k%2][i][j] = True
                        break

    return dp[n%2][0][0]

# 示例调用
print(isScramble("great", "rgeat"))  # 输出: true
print(isScramble("abcde", "caebd"))  # 输出: false
算法分析
  • 时间复杂度:(O(n^4)),虽然优化了空间复杂度,但时间复杂度仍然高。
  • 空间复杂度:(O(n^2)),使用滚动数组技术,只存储当前长度和前一长度的状态。

方法五:字符串哈希

解题步骤
  1. 预处理哈希值:为 s1s2 中的所有子字符串计算哈希值,以便快速比较任何两个子字符串是否相等。
  2. 动态规划:使用三维 dp 数组,其中 dp[i][j][len] 表示 s1 从位置 i 开始长度为 len 的子串与 s2 从位置 j 开始长度为 len 的子串是否是扰乱字符串。
完整的规范代码
def isScramble(s1, s2):
    """
    使用字符串哈希和动态规划判断 s2 是否是 s1 的扰乱字符串
    :param s1: str, 原始字符串
    :param s2: str, 可能的扰乱字符串
    :return: bool
    """
    if len(s1) != len(s2) or sorted(s1) != sorted(s2):
        return False

    n = len(s1)
    dp = [[[False] * (n + 1) for _ in range(n)] for __ in range(n)]

    # 字符串哈希预处理
    # 假设 hash_function 是一个用于计算字符串哈希的函数
    hash1 = [[0] * (n + 1) for _ in range(n)]
    hash2 = [[0] * (n + 1) for _ in range(n)]

    # 填充哈希数组
    for i in range(n):
        for j in range(i, n):
            hash1[i][j + 1] = hash_function(s1[i:j + 1])
            hash2[i][j + 1] = hash_function(s2[i:j + 1])

    # 初始化长度为1的情况
    for i in range(n):
        for j in range(n):
            dp[i][j][1] = s1[i] == s2[j]

    # 动态规划
    for length in range(2, n + 1):
        for i in range(n - length + 1):
            for j in range(n - length + 1):
                for k in range(1, length):
                    if (dp[i][j][k] and dp[i + k][j + k][length - k]) or (dp[i][j + length - k][k] and dp[i + k][j][length - k]):
                        dp[i][j][length] = True
                        break

    return dp[0][0][n]

# 示例调用
print(isScramble("great", "rgeat"))  # 输出: true
print(isScramble("abcde", "caebd"))  # 输出: false
算法分析
  • 时间复杂度:在优化情况下接近 (O(n^3))。预处理哈希值和填充动态规划表的过程都涉及三层循环。
  • 空间复杂度:哈希表的大小和动态规划表的大小都为 (O(n^2)),因此总体空间复杂度为 (O(n^2))。

这种方法利用字符串哈希来优化字符串的比较过程,大幅减少了时间开销,特别适用于字符串长度较大的情况。

不同算法的优劣势对比

特征方法一:递归方法二:记忆化搜索方法三:动态规划方法四:优化的动态规划方法五:字符串哈希
时间复杂度(O(n^4))(O(n^3))(O(n^4))(O(n^4))(O(n^3)) 至 (O(n^4))
空间复杂度(O(n))(O(n^2))(O(n^3))(O(n^2))(O(n^2)) 至 (O(n^3))
优势直观简单减少重复计算精确控制空间优化快速字符串比较
劣势复杂度高空间较大空间消耗大复杂度依然高需要高效的哈希实现

应用示例

这类算法在处理文本编辑器中的文字扰乱问题、编译器中的代码重构检测、以及一些需要大规模字符串操作的生物信息学应用中尤为重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据分析螺丝钉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值