使用动态规划解决最大UCC子串问题 | 豆包MarsCode AI刷题

180 阅读5分钟

题目链接:最大UCC子串计算 - MarsCode

问题描述

小S有一个由字符 'U' 和 'C' 组成的字符串 SS,并希望在编辑距离不超过给定值 mm 的条件下,尽可能多地在字符串中找到 "UCC" 子串。

编辑距离定义为将字符串 SS 转化为其他字符串时所需的最少编辑操作次数。允许的每次编辑操作是插入、删除或替换单个字符。你需要计算在给定的编辑距离限制 mm 下,能够包含最多 "UCC" 子串的字符串可能包含多少个这样的子串。

例如,对于字符串"UCUUCCCCC"和编辑距离限制m = 3,可以通过编辑字符串生成最多包含3个"UCC"子串的序列。

样例1:可以将字符串修改为 "UCCUCCUCC"(2 次替换操作,不超过给定值 m = 3),包含 3 个 "UCC" 子串。

样例2:后面插入 5 个字符 "CCUCC"(5 次插入操作,不超过给定值 m = 6),可以将字符串修改为 "UCCUCC",包含 2 个 "UCC" 子串。

样例3:替换最后 2 个字符,可以将字符串修改为 "UCCUCC",包含 2 个 "UCC" 子串。

虽然分类到了hard题,也确实需要一些精力考虑解决,但注意到以下几点,有助于简化问题:

  1. 每个字符只有两种可能:非 UC,(类似二进制数位?),就意味着对单个字符,最多改一次可满足条件
  2. 要尽可能多的子串,又可以替换,因此没必要进行删除
  3. 一次编辑只能增一个字符,故至少要长度之差n次编辑。

可以在中间插入字符,导致串内容会变,不能确定窗口或指针。但可拆分子问题(结尾的串能被加上),可以看出是比较典型的动态规划问题。

假设状态为 dp[i][j],表示原始串前 i 个字符中,通过 j 次编辑,最多能生 "UCC" 子串数量。答案为 dp[n][m]

初始时:

  • 长度为0的字符串,每三次编辑(追加UCC后缀)才能得到一个子串,即dp[0][j] = j//3
  • 长度为 i 的字符串不经过编辑,dp[i][0] 为原串中匹配 UCC 的数量。

由于状态转移过程比较多,建一个临时列表存储所有可能值。

  • dp[i-1][j]:不做操作,或加入最后一个字母不能形成新子串:等同于i-1位前缀。
  • dp[i-1][j-2]+1:长度大于1的所有串:结尾为 U,追加 CC;结尾为 C,前面插入 U,后面插入 C
  • dp[i-3][j]+1:结尾是UCC
  • dp[i-2][j-1]+1:对于结尾为 CCUC 插入一个字母形成 UCC

dp[i][j] 取以上可能的最大值。

PS:AI给出的思路如下:

我们需要在一个由字符 'U' 和 'C' 组成的字符串中,通过不超过给定的编辑距离 m,尽可能多地找到 "UCC" 子串。编辑操作包括插入、删除和替换单个字符。

数据结构选择

我们可以使用动态规划(Dynamic Programming, DP)来解决这个问题。动态规划是一种通过将问题分解为子问题并存储子问题的解来解决复杂问题的方法。

算法步骤

  1. 定义状态

    • dp[i][j] 表示字符串 s 的前 i 个字符中,通过不超过 j 次编辑操作,最多能生成多少个 "UCC" 子串。
  2. 初始化

    • 当 i = 0 时,dp[0][j] 表示空字符串通过 j 次编辑操作能生成多少个 "UCC" 子串。显然,空字符串最多能生成 j // 3 个 "UCC" 子串(因为每次插入操作最多能生成一个 "UCC")。
  3. 状态转移

    • 对于每个字符 s[i-1],我们有几种选择:

      • 不形成新的 "UCC" 子串,忽略最后一个字母。
      • 通过插入两个字母形成 "UCC"
      • 如果当前字符是 "UCC" 的一部分,则可以形成一个新的 "UCC" 子串。
      • 如果当前字符是 "C",则可以通过插入一个字母形成 "UCC"
  4. 最终结果

    • dp[n][m] 即为字符串 s 的前 n 个字符中,通过不超过 m 次编辑操作,最多能生成多少个 "UCC" 子串。

思路总结

通过动态规划,我们可以逐步构建出在不同编辑距离限制下,最多能生成的 "UCC" 子串的数量。关键在于合理定义状态和状态转移方程,确保每一步的编辑操作都能被正确计算。

AC代码如下:

def solution(m: int, s: str) -> int:
    # write code here
    n = len(s)
    dp = [[0] * (m + 1) for _ in range(n + 1)]  # s 的前 i 个字符中,通过不超过 j 次编辑操作,最多能生成多少个 "UCC" 子串。

    # 初始化数组
    for i in range(m+1):
        dp[0][i] = i//3
    a0 = 0
    for i in range(2, n+1):
        if s[i-3: i] == "UCC":
            a0 += 1
        dp[i][0] = a0

    for i in range(1, n+1):
        for j in range(1, m+1):
            temp = [dp[i-1][j]]     # 不形成新的子串,忽略最后一个字母
            if j > 1:
                temp.append(dp[i-1][j-2]+1)     # 对于结尾任意一个字母,插入两个字母形成 "UCC"
            if i >= 3 and s[i-3: i] == "UCC":
                temp.append(dp[i-3][j]+1)       # 以 "UCC" 结尾
            elif i > 1 and s[i-1] == 'C':
                temp.append(dp[i-2][j-1]+1)     # 对于结尾为 "CC" 或 “UC” 插入一个字母形成 "UCC"
            dp[i][j] = max(temp)
    print(dp)
    return dp[n][m]

if __name__ == '__main__':
    print(solution(m=3, s="UCUUCCCCC") == 3)
    print(solution(m=6, s="U") == 2)
    print(solution(m=2, s="UCCUUU") == 2)
    print(solution(m=3, s="CUUUCUCUUUUCU") == 3)