问题描述
小S有一个由字符 'U' 和 'C' 组成的字符串 SS,并希望在编辑距离不超过给定值 mm 的条件下,尽可能多地在字符串中找到 "UCC" 子串。
编辑距离定义为将字符串 SS 转化为其他字符串时所需的最少编辑操作次数。允许的每次编辑操作是插入、删除或替换单个字符。你需要计算在给定的编辑距离限制 mm 下,能够包含最多 "UCC" 子串的字符串可能包含多少个这样的子串。
例如,对于字符串"UCUUCCCCC"和编辑距离限制m = 3,可以通过编辑字符串生成最多包含3个"UCC"子串的序列。
题目解析与思路
-
编辑距离的概念: 编辑距离是指通过插入、删除或替换字符,将一个字符串转换成另一个字符串所需要的最小操作次数。在本题中,编辑操作包括:
- 插入:在字符串中插入字符。
- 删除:从字符串中删除字符。
- 替换:替换字符串中的某个字符。
-
目标: 我们需要在给定的编辑距离限制
m内,修改字符串s,使得字符串中包含尽可能多的 "UCC" 子串。 -
动态规划: 动态规划(DP)是解决这个问题的核心。我们可以使用一个 DP 表
dp[i][j]来表示,从字符串s[0...i-1]中提取出j个 "UCC" 子串所需要的最小编辑距离。 -
状态转移:
-
如果我们要从前
i个字符提取出j个 "UCC" 子串,那么我们考虑以下几种操作:- 不形成新子串:此时,我们可以选择删除字符或不做任何修改。
- 形成新子串:如果当前字符可以通过编辑变成 "UCC",则我们可以通过替换操作来增加一个新的 "UCC" 子串。
-
-
编辑成本计算: 每次试图将三个连续字符变成 "UCC" 时,需要计算编辑成本。这个成本是替换每个字符的次数,替换的目标是 "UCC"。
-
最终目标: 在字符串的所有字符中,我们尽量多地形成 "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
-
初始化:
dp[i][j]:表示使用前i个字符能够形成j个 "UCC" 子串的最小编辑距离。- 初始状态
dp[0][0] = 0,表示从空字符串开始,形成 0 个 "UCC" 子串,不需要任何操作。 dp[i][j] = float('inf')表示无穷大,表示尚未计算或无法通过编辑实现。
-
状态转移:
- 对每一个字符
i和每一个可能的 "UCC" 子串数量j,我们首先考虑不形成新的 "UCC" 子串,直接继承前一个状态dp[i-1][j]。 - 然后我们尝试通过编辑当前的三个字符(
s[i-3], s[i-2], s[i-1])来形成新的 "UCC" 子串,计算替换的成本,并且如果这个操作在编辑距离限制内,则更新状态。
- 对每一个字符
-
最终结果:
- 遍历
dp[n][j](所有使用了n个字符的状态),找出最大j使得编辑距离不超过m。
- 遍历
知识点总结
- 二维列表初始化:
dp = [[float('inf')] * (n // 3 + 1) for _ in range(n + 1)]用于初始化一个二维数组,表示所有状态都设为无穷大,表示初始状态是无法达到的。 - 字符串操作:我们使用字符串 "UCC" 来进行匹配。
"UCC"[2 - k]是为了从后往前匹配字符UCC,这对于编辑距离的计算是很方便的。 - 动态规划:使用动态规划来计算状态的转移,避免了重复计算,提高了算法的效率。
- 列表与元组的索引:通过
i-1和i-2等索引操作,可以方便地访问字符串中的字符,进行编辑操作。
通过动态规划解决这个问题,关键是状态表示和转移。我们使用 dp[i][j] 表示前 i 个字符可以构成 j 个 "UCC" 子串所需的最小编辑距离。通过逐步计算和更新状态,最终可以得到在编辑距离不超过 m 的条件下,能够包含的最大 "UCC" 子串数量。