问题描述
小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" 子串。
思路解析
问题解析
本题的目标是:
-
在编辑距离不超过 m 的条件下,计算字符串中最多可以形成多少个 “UCC” 子串。
-
编辑操作包括插入、删除和替换。
• 目标是让字符串中包含尽可能多的 “UCC”。
• 如果某段连续子串中已经包含 “UCC”,则无需操作。
• 如果某段子串和 “UCC” 相似但不完全匹配,则可以通过少量编辑操作(替换或插入)将其调整为 “UCC”。
- 滑动窗口检查可能的子串:
• 使用长度为 3 的滑动窗口,逐段扫描字符串。
• 对于每个窗口,计算将当前子串调整为 “UCC” 所需的编辑距离。
• 如果需要的编辑操作不超过 m ,记录一个子串,并减少剩余的 m 。
- 贪心策略扩展更多子串:
• 优先选择编辑距离最小的子串(从左向右扫描时)。
• 每次编辑后,从当前位置继续向右寻找,避免重复统计。
- 边界条件:
• 如果 m 已用尽,则停止搜索。
• 如果字符串长度不足 3,则无法形成 “UCC” 子串。
关键点:
• 每个 “UCC” 子串占据 3 个字符位置,若编辑距离允许,可以通过添加或修改字符,使得字符串形成更多的 “UCC”。
• 如何选择插入和替换位置,最大化子串数量是本题的核心。
动态规划优化
为了更高效地解决问题,可以利用动态规划(DP)方法,逐段计算子串的最大数量。
定义 DP 状态:
• dp[i][j] :表示考虑前 i 个字符,且使用编辑距离不超过 j 时,可以形成的最大 “UCC” 子串数。
状态转移方程:
-
不匹配当前子串:如果不使用当前位置形成 “UCC”:
dp[i][j] = dp[i-1][j]
-
匹配当前子串:如果尝试将以第 i-3 到 i-1 个字符为结尾的子串调整为 “UCC”,需要消耗 cost 的编辑距离:
dp[i][j] = max(dp[i][j], dp[i-3][j-cost] + 1)
其中 cost 为将当前子串调整为 “UCC” 的编辑距离。
- 选择两者的最大值。
算法过程
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. 实际应用:
• 该问题可以拓展到区间操作、数组离散化等场景,适用于需要模拟分布调整的优化问题。