最大UCC字串计算 | python 解法
解题思路
1. 问题分析
题目要求在给定的字符串 s 中,最大限度地匹配 "UCC" 子串,且编辑距离不能超过给定的最大值 m。编辑距离的操作包括:插入字符、删除字符和替换字符,每次操作会消耗一定的编辑次数。需要在编辑距离不超过 m 的前提下,使得字符串中包含尽可能多的 "UCC" 子串。
问题关键点:
- 字符串
s中由字符'U'和'C'组成,可以通过有限的编辑操作将字符串修改为包含更多的 "UCC" 子串。 - 编辑操作的次数必须小于等于给定的
m,并且通过这些编辑操作最大化 "UCC" 子串的数量。 - 编辑操作包括插入字符、删除字符和替换字符。每种操作会消耗 1 次编辑操作。
需要设计一个算法,来在不超过 m 次编辑操作的前提下,计算出能够找到的最多 "UCC" 子串的数量。
2. 算法使用
这个问题属于典型的最优化问题,适合使用 动态规划(DP)算法来求解。动态规划是一种通过分阶段求解子问题来解决复杂问题的策略,在此问题中,可以通过记录子问题的解(例如,前面若干字符经过 k 次编辑后能够匹配的 "UCC" 数量),逐步构建出最终的解。
3. 算法流程
-
定义状态: 定义
dp[i][j]表示前i个字符,使用j次编辑操作时,最多能够匹配的 "UCC" 子串的数量。目标是计算出dp[len(s)][m]的最大值。 -
初始化:
- 初始化
dp[0][0] = 0,即表示空字符串,编辑操作为0时,匹配的 "UCC" 子串数量为0。 - 其他
dp[i][j]初始值为-1,表示无法通过j次编辑操作匹配前i个字符。
- 初始化
-
匹配 "UCC" 子串:
- 对于字符串中的每个字符,尝试从该字符开始匹配 "UCC" 子串,并记录最小编辑距离。对于每个字符,从当前位置开始匹配 "UCC" 子串,需要计算最小的编辑距离。
- 为了匹配 "UCC" 子串,根据当前字符是
U还是C来选择是插入、替换还是保留字符。 - 通过动态规划,可以得到从某个位置开始匹配 "UCC" 的最小编辑距离。
-
状态转移:
-
对于每个位置
i和编辑操作数j,可以进行以下三种操作:- 保留字符:即当前字符不进行编辑,继续匹配下一个字符。
- 删除字符:即删除当前字符,编辑次数加 1。
- 替换字符:即将当前字符替换为目标字符(如果不相同),编辑次数加 1。
-
通过这些操作,逐步更新
dp表,并记录每种情况下的最大匹配数。
-
-
最终结果:
- 通过动态规划填表后,最终的结果是
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)。
- 计算每个字符的最小编辑距离和匹配的 "UCC" 子串,计算的时间复杂度为
-
空间复杂度:
- 使用了
dp_table来存储每个状态,大小为O(n * m)。 - 另外,
match_details用于存储每个字符的匹配信息,空间复杂度为O(n * 3)。 - 因此,总的空间复杂度为
O(n * m)。
- 使用了
6. 可拓展的知识点
- 动态规划优化: 如果状态转移只依赖于当前和前一行的结果,可以通过滚动数组来优化空间复杂度。只需要存储当前行和前一行的状态,从而将空间复杂度降低为
O(m)。 - 编辑距离的变种: 在实际应用中,编辑距离问题常常会有一些变种,比如不同的操作代价(替换、删除、插入的代价不同),或者存在额外的限制(例如,某些字符必须保留)。这些变种可以通过调整状态转移的逻辑来解决。
- 字符串匹配问题: 类似的字符串匹配问题可以应用动态规划来求解,如最长公共子序列(LCS)问题、最短编辑距离问题等。
通过这种方式,可以够通过有限的编辑操作来使字符串 s 包含最多的 "UCC" 子串,并保证不超过 m 次编辑操作。