青训营X豆包 MarsCode AI | 豆包MarsCode AI刷题

152 阅读6分钟

最大UCC字串计算 | python 解法

解题思路

1. 问题分析

题目要求在给定的字符串 s 中,最大限度地匹配 "UCC" 子串,且编辑距离不能超过给定的最大值 m。编辑距离的操作包括:插入字符、删除字符和替换字符,每次操作会消耗一定的编辑次数。需要在编辑距离不超过 m 的前提下,使得字符串中包含尽可能多的 "UCC" 子串。

问题关键点:

  • 字符串 s 中由字符 'U''C' 组成,可以通过有限的编辑操作将字符串修改为包含更多的 "UCC" 子串。
  • 编辑操作的次数必须小于等于给定的 m,并且通过这些编辑操作最大化 "UCC" 子串的数量。
  • 编辑操作包括插入字符、删除字符和替换字符。每种操作会消耗 1 次编辑操作。

需要设计一个算法,来在不超过 m 次编辑操作的前提下,计算出能够找到的最多 "UCC" 子串的数量。

2. 算法使用

这个问题属于典型的最优化问题,适合使用 动态规划(DP)算法来求解。动态规划是一种通过分阶段求解子问题来解决复杂问题的策略,在此问题中,可以通过记录子问题的解(例如,前面若干字符经过 k 次编辑后能够匹配的 "UCC" 数量),逐步构建出最终的解。

3. 算法流程

  1. 定义状态: 定义 dp[i][j] 表示前 i 个字符,使用 j 次编辑操作时,最多能够匹配的 "UCC" 子串的数量。目标是计算出 dp[len(s)][m] 的最大值。

  2. 初始化:

    • 初始化 dp[0][0] = 0,即表示空字符串,编辑操作为0时,匹配的 "UCC" 子串数量为0。
    • 其他 dp[i][j] 初始值为 -1,表示无法通过 j 次编辑操作匹配前 i 个字符。
  3. 匹配 "UCC" 子串:

    • 对于字符串中的每个字符,尝试从该字符开始匹配 "UCC" 子串,并记录最小编辑距离。对于每个字符,从当前位置开始匹配 "UCC" 子串,需要计算最小的编辑距离。
    • 为了匹配 "UCC" 子串,根据当前字符是 U 还是 C 来选择是插入、替换还是保留字符。
    • 通过动态规划,可以得到从某个位置开始匹配 "UCC" 的最小编辑距离。
  4. 状态转移:

    • 对于每个位置 i 和编辑操作数 j,可以进行以下三种操作:

      • 保留字符:即当前字符不进行编辑,继续匹配下一个字符。
      • 删除字符:即删除当前字符,编辑次数加 1。
      • 替换字符:即将当前字符替换为目标字符(如果不相同),编辑次数加 1。
    • 通过这些操作,逐步更新 dp 表,并记录每种情况下的最大匹配数。

  5. 最终结果:

    • 通过动态规划填表后,最终的结果是 dp[len(s)][m],即在给定编辑距离限制下,能够匹配的最多 "UCC" 子串数量。

4. 代码实现

def maximize_substrings(m: int, s: str) -> int:
    len_s = len(s)

    # dp[i][j]表示前i个字符,使用j次编辑操作时,最多能匹配的 "UCC" 子串数量
    dp_table = [[-1] * (m + 1) for _ in range(len_s + 1)]
    dp_table[0][0] = 0  # 初始状态,编辑操作数为0,匹配数量为0

    # match_info[i] 记录从索引i开始匹配 "UCC" 时的编辑距离和匹配长度
    match_details = [[] for _ in range(len_s)]
    
    # 计算从每个字符开始,能够匹配 "UCC" 子串的最小编辑距离
    for i in range(len_s):
        max_len = min(len_s - i, 3 + m)  # 允许匹配的最大长度为3 + m
        temp_dp = [[float('inf')] * (max_len + 1) for _ in range(4)]  # temp_dp 用于存储匹配进度
        temp_dp[0][0] = 0  # 初始状态,空串匹配,编辑距离为0

        # 计算匹配 "UCC" 的最小编辑距离
        for progress in range(4):  # 进度:0 -> '',1 -> 'U',2 -> 'UC',3 -> 'UCC'
            for matched_len in range(max_len + 1):  # 当前已匹配字符的长度
                if temp_dp[progress][matched_len] > m:  # 超过最大编辑次数,跳过
                    continue
                if progress < 3 and matched_len < max_len:  # 保留或者替换字符
                    cost = 0 if s[i + matched_len] == 'UCC'[progress] else 1
                    temp_dp[progress + 1][matched_len + 1] = min(temp_dp[progress + 1][matched_len + 1], temp_dp[progress][matched_len] + cost)
                if progress < 3:  # 插入字符
                    temp_dp[progress + 1][matched_len] = min(temp_dp[progress + 1][matched_len], temp_dp[progress][matched_len] + 1)
                if matched_len < max_len:  # 删除字符
                    temp_dp[progress][matched_len + 1] = min(temp_dp[progress][matched_len + 1], temp_dp[progress][matched_len] + 1)

        # 记录匹配 "UCC" 的最小编辑距离和匹配长度
        for matched_len in range(max_len + 1):
            edit_cost = temp_dp[3][matched_len]
            match_details[i].append((edit_cost, matched_len))  # (编辑距离, 匹配长度)

    # 动态规划:dp_table[i][e] 表示前i个字符,使用e次编辑操作时,最多能匹配的 "UCC" 子串数
    for idx in range(len_s + 1):
        for edit_count in range(m + 1):
            if dp_table[idx][edit_count] == -1:  # 当前状态无法到达
                continue
            if idx < len_s:  # 不尝试匹配 "UCC",直接删除或保留当前字符
                dp_table[idx + 1][edit_count] = max(dp_table[idx + 1][edit_count], dp_table[idx][edit_count])  # 保留
                if edit_count + 1 <= m:  # 删除当前字符
                    dp_table[idx + 1][edit_count + 1] = max(dp_table[idx + 1][edit_count + 1], dp_table[idx][edit_count])
            if idx < len_s and match_details[idx]:  # 尝试匹配 "UCC"
                for edit_cost, match_len in match_details[idx]:  # edit_cost是编辑距离,match_len是匹配的长度
                    if edit_count + edit_cost <= m and idx + match_len <= len_s:
                        dp_table[idx + match_len][edit_count + edit_cost] = max(dp_table[idx + match_len][edit_count + edit_cost], dp_table[idx][edit_count] + 1)

    # 寻找最大可匹配的 "UCC" 子串数
    max_matches = 0
    for edit_count in range(m + 1):
        max_matches = max(max_matches, dp_table[len_s][edit_count])  # 查找最大值
    return max_matches

5. 复杂度分析

  • 时间复杂度:

    • 计算每个字符的最小编辑距离和匹配的 "UCC" 子串,计算的时间复杂度为 O(n * m * 3),其中 n 为字符串的长度,m 为编辑操作的最大次数,3 表示 "UCC" 子串的最大长度。
    • 最终的动态规划填表时间复杂度为 O(n * m),因为要遍历所有的状态 dp[i][j]
    • 总的时间复杂度为 O(n * m * 3),即 O(n * m)
  • 空间复杂度:

    • 使用了 dp_table 来存储每个状态,大小为 O(n * m)
    • 另外,match_details 用于存储每个字符的匹配信息,空间复杂度为 O(n * 3)
    • 因此,总的空间复杂度为 O(n * m)

6. 可拓展的知识点

  1. 动态规划优化: 如果状态转移只依赖于当前和前一行的结果,可以通过滚动数组来优化空间复杂度。只需要存储当前行和前一行的状态,从而将空间复杂度降低为 O(m)
  2. 编辑距离的变种: 在实际应用中,编辑距离问题常常会有一些变种,比如不同的操作代价(替换、删除、插入的代价不同),或者存在额外的限制(例如,某些字符必须保留)。这些变种可以通过调整状态转移的逻辑来解决。
  3. 字符串匹配问题: 类似的字符串匹配问题可以应用动态规划来求解,如最长公共子序列(LCS)问题、最短编辑距离问题等。

通过这种方式,可以够通过有限的编辑操作来使字符串 s 包含最多的 "UCC" 子串,并保证不超过 m 次编辑操作。