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

115 阅读5分钟

问题描述

小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" 子串。

思路解析

问题解析

本题的目标是:

  1. 在编辑距离不超过  m  的条件下,计算字符串中最多可以形成多少个 “UCC” 子串。

  2. 编辑操作包括插入删除替换

• 目标是让字符串中包含尽可能多的 “UCC”。

• 如果某段连续子串中已经包含 “UCC”,则无需操作。

• 如果某段子串和 “UCC” 相似但不完全匹配,则可以通过少量编辑操作(替换或插入)将其调整为 “UCC”。

  1. 滑动窗口检查可能的子串

• 使用长度为 3 的滑动窗口,逐段扫描字符串。

• 对于每个窗口,计算将当前子串调整为 “UCC” 所需的编辑距离。

• 如果需要的编辑操作不超过  m ,记录一个子串,并减少剩余的  m 。

  1. 贪心策略扩展更多子串

• 优先选择编辑距离最小的子串(从左向右扫描时)。

• 每次编辑后,从当前位置继续向右寻找,避免重复统计。

  1. 边界条件

• 如果  m  已用尽,则停止搜索。

• 如果字符串长度不足 3,则无法形成 “UCC” 子串。

关键点

• 每个 “UCC” 子串占据 3 个字符位置,若编辑距离允许,可以通过添加或修改字符,使得字符串形成更多的 “UCC”。

• 如何选择插入和替换位置,最大化子串数量是本题的核心。

动态规划优化

为了更高效地解决问题,可以利用动态规划(DP)方法,逐段计算子串的最大数量。

定义 DP 状态

•  dp[i][j] :表示考虑前  i  个字符,且使用编辑距离不超过  j  时,可以形成的最大 “UCC” 子串数。

状态转移方程

  1. 不匹配当前子串:如果不使用当前位置形成 “UCC”:

dp[i][j] = dp[i-1][j]

  1. 匹配当前子串:如果尝试将以第  i-3  到  i-1  个字符为结尾的子串调整为 “UCC”,需要消耗  cost  的编辑距离:

dp[i][j] = max(dp[i][j], dp[i-3][j-cost] + 1)

其中  cost  为将当前子串调整为 “UCC” 的编辑距离。

  1. 选择两者的最大值。

算法过程

def max_ucc_substrings(s, m):
    n = len(s)
    if n < 3:
        return 0  # 字符串长度不足以形成 UCC
    
    # 计算将任意子串调整为 "UCC" 的编辑距离
    def edit_distance_to_ucc(substr):
        target = "UCC"
        return sum(1 for a, b in zip(substr, target) if a != b)
    
    dp = [[0] * (m + 1) for _ in range(n + 1)]
    
    for i in range(3, n + 1):  # 从第 3 个字符开始,形成长度为 3 的窗口
        for j in range(m + 1):  # 遍历所有可能的编辑距离
            # 不匹配当前窗口
            dp[i][j] = dp[i-1][j]
            
            # 尝试匹配当前窗口
            substr = s[i-3:i]  # 长度为 3 的子串
            cost = edit_distance_to_ucc(substr)
            if j >= cost:
                dp[i][j] = max(dp[i][j], dp[i-3][j-cost] + 1)
    
    return max(dp[-1])  # 返回在最大编辑距离下的最大子串数

# 测试样例
s = "UCUUCCCCC"
m = 3
print(max_ucc_substrings(s, m))  # 输出:3

思考与分析

1. 为什么排序是必要的?

• 排序后,石子的分布就可以通过简单的间距计算判断是否连续,同时能够轻松确定端点石子。

2. 最大移动次数的直观理解:

• 最大移动次数对应于从当前石子分布填补所有间隙所需的操作次数。例如,在 stones = [7, 4, 9] 中,初始状态有两个间隙:

• 从 4 到 7,有一个间距 2。

• 从 7 到 9,有一个间距 1。

合计间隙数量为 3,扣除连续的  (n-1 = 2)  后,最大移动次数为 2。

3. 延伸问题:如何找到最小移动次数?

• 另一个可能的挑战是计算最少移动次数,这需要考虑具体的移动策略。通过滑动窗口方法可以快速找到填补所有间隙所需的最少操作次数。

4. 实际应用:

• 该问题可以拓展到区间操作、数组离散化等场景,适用于需要模拟分布调整的优化问题。