编程笔记:最大UCC子串计算 | 豆包MarsCode AI刷题

82 阅读5分钟

问题描述

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

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

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

题目解析与思路

  1. 编辑距离的概念: 编辑距离是指通过插入、删除或替换字符,将一个字符串转换成另一个字符串所需要的最小操作次数。在本题中,编辑操作包括:

    • 插入:在字符串中插入字符。
    • 删除:从字符串中删除字符。
    • 替换:替换字符串中的某个字符。
  2. 目标: 我们需要在给定的编辑距离限制 m 内,修改字符串 s,使得字符串中包含尽可能多的 "UCC" 子串。

  3. 动态规划: 动态规划(DP)是解决这个问题的核心。我们可以使用一个 DP 表 dp[i][j] 来表示,从字符串 s[0...i-1] 中提取出 j 个 "UCC" 子串所需要的最小编辑距离。

  4. 状态转移

    • 如果我们要从前 i 个字符提取出 j 个 "UCC" 子串,那么我们考虑以下几种操作:

      • 不形成新子串:此时,我们可以选择删除字符或不做任何修改。
      • 形成新子串:如果当前字符可以通过编辑变成 "UCC",则我们可以通过替换操作来增加一个新的 "UCC" 子串。
  5. 编辑成本计算: 每次试图将三个连续字符变成 "UCC" 时,需要计算编辑成本。这个成本是替换每个字符的次数,替换的目标是 "UCC"。

  6. 最终目标: 在字符串的所有字符中,我们尽量多地形成 "UCC" 子串,同时编辑距离不超过 m

代码详解

def solution(m: int, s: str) -> int:
    n = len(s)

    # dp[i][j] 表示使用前 i 个字符,能够形成 j 个 "UCC" 子串的最小编辑距离
    dp = [[float('inf')] * (n // 3 + 1) for _ in range(n + 1)]
    dp[0][0] = 0  # 从空字符串开始,0 个 "UCC" 子串,不需要任何操作

    # 遍历每个字符,构建状态转移
    for i in range(1, n + 1):
        for j in range(n // 3 + 1):
            # 如果我们没有形成任何 "UCC" 子串,或者要继续构造 j 个 "UCC"
            
            # 情况 1:我们不构成 "UCC" 子串,直接删除或替换字符
            dp[i][j] = min(dp[i][j], dp[i - 1][j])  # 维持当前字符不变
            
            # 情况 2:构造新的 "UCC" 子串
            if j > 0 and i >= 3:  # 要确保我们有足够的字符可以形成 "UCC"
                # 计算把当前 i-3, i-2, i-1 位置的字符变成 "UCC" 所需要的编辑距离
                cost_replace = 0
                for k in range(3):
                    if s[i - 1 - k] != "UCC"[2 - k]:
                        cost_replace += 1
                
                # 如果我们能通过编辑得到新的 "UCC" 且编辑距离在限制内
                if dp[i - 3][j - 1] + cost_replace <= m:
                    dp[i][j] = min(dp[i][j], dp[i - 3][j - 1] + cost_replace)
    
    # 查找最大 j,使得 dp[n][j] <= m
    result = 0
    for j in range(n // 3 + 1):
        if dp[n][j] <= m:
            result = max(result, j)

    return result
  1. 初始化

    • dp[i][j]:表示使用前 i 个字符能够形成 j 个 "UCC" 子串的最小编辑距离。
    • 初始状态 dp[0][0] = 0,表示从空字符串开始,形成 0 个 "UCC" 子串,不需要任何操作。
    • dp[i][j] = float('inf') 表示无穷大,表示尚未计算或无法通过编辑实现。
  2. 状态转移

    • 对每一个字符 i 和每一个可能的 "UCC" 子串数量 j,我们首先考虑不形成新的 "UCC" 子串,直接继承前一个状态 dp[i-1][j]
    • 然后我们尝试通过编辑当前的三个字符(s[i-3], s[i-2], s[i-1])来形成新的 "UCC" 子串,计算替换的成本,并且如果这个操作在编辑距离限制内,则更新状态。
  3. 最终结果

    • 遍历 dp[n][j](所有使用了 n 个字符的状态),找出最大 j 使得编辑距离不超过 m

知识点总结

  1. 二维列表初始化dp = [[float('inf')] * (n // 3 + 1) for _ in range(n + 1)] 用于初始化一个二维数组,表示所有状态都设为无穷大,表示初始状态是无法达到的。
  2. 字符串操作:我们使用字符串 "UCC" 来进行匹配。"UCC"[2 - k] 是为了从后往前匹配字符 UCC,这对于编辑距离的计算是很方便的。
  3. 动态规划:使用动态规划来计算状态的转移,避免了重复计算,提高了算法的效率。
  4. 列表与元组的索引:通过 i-1i-2 等索引操作,可以方便地访问字符串中的字符,进行编辑操作。

通过动态规划解决这个问题,关键是状态表示和转移。我们使用 dp[i][j] 表示前 i 个字符可以构成 j 个 "UCC" 子串所需的最小编辑距离。通过逐步计算和更新状态,最终可以得到在编辑距离不超过 m 的条件下,能够包含的最大 "UCC" 子串数量。