最大UCC子串计算题解| 豆包MarsCode AI刷题

338 阅读5分钟

思路来自CSDN的分享

问题描述

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

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

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


测试样例

样例1:

输入:m = 3,s = "UCUUCCCCC"
输出:3

样例2:

输入:m = 6,s = "U"
输出:2

样例3:

输入:m = 2,s = "UCCUUU"
输出:2

解释

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

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

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

解题思路

之前学习天大机试的时候就有遇到过这类字符题,不会写,正好学习一下

采用dp思想,dp 和 dp_matchdp 表记录了在不同编辑距离下,前 i 个字符中最多能包含多少个 "UCC" 子串;dp_match 表记录了从当前字符开始匹配 "UCC" 的最小编辑距离和匹配成功时的长度。最终通过遍历 dp 表找到最大匹配数量。

这里有关字符串的方案问题比较麻烦的就是涉及到的状态转换问题,这里涉及两次dp计算

1、- match_info = [[] for _ in range(n)]:初始化一个列表 match_info,其中 match_info[i] 存储从 s[i] 开始匹配 "UCC" 的最小编辑距离和匹配成功时的长度。

  • for i in range(n):遍历字符串 s 的每个字符。
  • max_len = min(n - i, 3 + m):计算从当前字符 s[i] 开始,匹配成功时可能达到的最大长度。
  • dp_match = [[float('inf')] * (max_len + 1) for _ in range(4)]:初始化一个二维数组 dp_match,其中 dp_match[p][q] 表示从 s[i] 开始匹配 "UCC" 的进度为 p0 表示空字符串,1 表示 "U"2 表示 "UC"3 表示 "UCC"),匹配过程中划过的长度为 q 时的最小编辑距离。
  • dp_match[0][0] = 0:初始状态,从 s[i] 开始匹配空字符串,编辑距离为 0。
  • for p in range(4) 和 for q in range(max_len + 1):遍历匹配进度和匹配长度,更新 dp_match 表,考虑插入、删除和替换操作。
  • match_info[i].append((c, q)):将匹配结果存储到 match_info 中。

2、- for i in range(n + 1) 和 for e in range(m + 1):遍历 dp 表的每个状态。

  • if dp[i][e] == -1:如果当前状态未计算,跳过。
  • if i < n:如果当前字符不是最后一个字符,考虑不进行编辑操作(保留)和删除操作。
  • if i < n and match_info[i]:如果当前字符不是最后一个字符且有匹配信息,尝试匹配 "UCC",更新 dp 表。

代码

def solution(m: int, s: str) -> int:
    n = len(s)
    dp = [[-1] * (m + 1) for _ in range(n + 1)]  # 这里赋值-1?
    dp[0][0] = 0 
 
    # 第一次动态规划
    # 计算从每个字符开始,为了匹配 "UCC" 产生的最小编辑距离和匹配成功时的长度
    # 每个字符的计算过程都是dp
    match_info = [[] for _ in range(n)]  # match_info[i] = 从s[i]开始,匹配“UCC”的(最小编辑距离,匹配成功时的长度) 
    for i in range(n):
        max_len = min(n - i, 3 + m)  # 从当前字符s[i]开始,匹配成功时可能达到的最大长度
        # 从当前字符s[i]开始,匹配 "UCC" 的最小编辑距离
        dp_match = [[float('inf')] * (max_len + 1) for _ in range(4)] 
        dp_match[0][0] = 0
        for p in range(4):  # 从s[i]开始匹配"UCC" 的进度:‘’->‘U'->'UC'->'UCC’
            for q in range(max_len + 1):  # 匹配过程中划过的长度 = 0,1,...,max_len
                if dp_match[p][q] > m:  # 编辑次数用完了
                    continue
                if p < 3 and q < max_len:  # 保留/替换
                    cost = 0 if s[i + q] == 'UCC'[p] else 1
                    dp_match[p + 1][q + 1] = min(dp_match[p + 1][q + 1], dp_match[p][q] + cost)
                if p < 3:  # 插入
                    dp_match[p + 1][q] = min(dp_match[p + 1][q], dp_match[p][q] + 1)
                if q < max_len:  # 删除
                    dp_match[p][q + 1] = min(dp_match[p][q + 1], dp_match[p][q] + 1)
        # 统计
        for q in range(max_len + 1):
            c = dp_match[3][q]
            match_info[i].append((c, q))  # (编辑距离,匹配长度)
 
    # 主过程的动态规划:
    for i in range(n + 1):
        for e in range(m + 1):
            if dp[i][e] == -1:
                continue
            if i < n:  # 不尝试匹配 "UCC" --> 直接跳过/删除当前字符
                dp[i + 1][e] = max(dp[i + 1][e], dp[i][e])  # 保留
                if e + 1 <= m:  # 删除
                    dp[i + 1][e + 1] = max(dp[i + 1][e + 1], dp[i][e])            
            if i < n and match_info[i]: # 尝试匹配
                for c, l in match_info[i]:  # 从当前字符串开始匹配‘UCC’的(最小编辑距离,匹配成功时长度)
                    if e + c <= m and i + l <= n:
                        dp[i + l][e + c] = max(dp[i + l][e + c], dp[i][e] + 1)
 
    # 找到最大匹配数量
    max_substrings = 0
    for e in range(m + 1):
        max_substrings = max(max_substrings, dp[n][e])
    return max_substrings