问题描述
小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,对输入字符串s进行一系列操作(如替换、插入等字符操作,操作次数受限于m),使得最终生成的字符串中包含尽可能多的"UCC"子串,最后返回该子串的数量。 -
步骤拆解:
-
第一步:统计初始字符串中已有的 "UCC" 子串数量(以替换方式间接统计) :
- 首先将输入字符串
s中的所有"UCC"子串统一替换为一个特定的标识字符串,这里选择了"AAA",得到新字符串s1。例如对于输入"UCUUCCCCC",替换后s1可能就变成了和原字符串不同的一个新形式,其中"UCC"都变成了"AAA"。 - 通过
countString方法,以"AAA"在s1中出现的次数来间接确定原始字符串s中"UCC"子串的初始数量。因为每出现一次"AAA"就代表原来有一个"UCC",这样就完成了对初始已有"UCC"子串的统计。
- 首先将输入字符串
-
第二步:考虑通过合并 "UC" 和 "CC" 来生成 "UCC" 的情况:
- 将
s1中的"UC"和"CC"分别都替换为另一个标识字符串"BBB",得到新字符串s2。这样做的原因是把"UC"和"CC"看作是可以通过一次操作(比如替换操作)变成"UCC"的潜在部分,统计"BBB"的数量就相当于统计了有多少这样的潜在可转换部分。 - 根据
"BBB"的数量来消耗编辑距离m,每有一个"BBB"就意味着可以通过一次操作(消耗一次编辑距离)去尝试生成一个"UCC"子串,同时相应地增加总的"UCC"子串数量(把通过这种转换生成的也算进去)。 - 做完这一步后,需要判断编辑距离是否用完,如果
m已经小于等于0,意味着没有足够的编辑距离再进行后续操作了,此时直接返回当前统计的"UCC"子串数量加上剩余的编辑距离(可能是负数,表示超出了)。
- 将
-
第三步:考虑单独的 'U' 和 'C' 字符情况:
- 统计
s2中单独的'U'和'C'字符的数量,因为它们可以通过两次操作(比如插入一个缺失的字符然后再进行适当替换)组合成"UCC"子串。例如,如果有足够的编辑距离,一个单独的'U'和两个单独的'C'就有可能组成"UCC"。 - 按照每个单独字符算两次编辑操作来消耗剩余的编辑距离
m,每消耗两次编辑距离就可以尝试生成一个"UCC"子串,所以同时要增加总的"UCC"子串数量。同样,消耗完这部分编辑距离后,再次判断m是否小于等于0,如果是,则根据剩余编辑距离情况返回相应结果(这里要考虑到边界情况,比如剩余编辑距离为奇数时的处理)。
- 统计
-
第四步:利用剩余编辑距离生成子串(如果还有剩余) :
- 如果经过前面步骤后还有剩余编辑距离
m,假设可以合理利用每次编辑距离生成一个"UCC"子串(具体取决于题目允许的操作方式),将剩余编辑距离除以3(因为生成一个"UCC"理论上最多需要三次操作,比如三次替换等情况),把得到的数量加到总的"UCC"子串数量上,最后返回这个最终统计的数量。
- 如果经过前面步骤后还有剩余编辑距离
-
数学公式
-
统计子串出现次数公式:
子串出现次数原字符串长度替换子串为空字符串后的长度子串自身长度
即代码中的(s.length() - s.replace(sub, "").length()) / sub.length(),用于统计如"UCC"等目标子串在给定字符串中出现的次数。例如,原字符串s = "UCUUCCCCC",子串sub = "UCC",通过此公式可算出其出现次数。 -
编辑距离消耗及子串数量更新相关公式(针对不同情况) :
- 处理 “UC” 和 “CC” 情况:
新旧的数量子串数量新子串数量旧的数量
代码中体现为 m -= count2 和 count += count2,其中 count2 是 "BBB" 的数量,m 是编辑距离,通过这样的计算来消耗编辑距离并更新子串数量。
- 处理单独 “U” 和 “C” 情况:
新旧单独的数量单独的数量子串数量新子串数量旧单独的数量单独的数量
对应代码里的 m -= count3 * 2 和 count += count3,这里 count3 是单独 'U' 和 'C' 的数量总和,按每个单独字符算两次编辑操作来消耗编辑距离并更新子串数量。
- 利用剩余编辑距离生成子串情况:
可新增子串数量剩余
代码中最后返回结果部分的 count + m / 3 体现了这一公式(这里除法取整),基于剩余编辑距离 m 按生成一个 "UCC" 子串最多需 3 次操作来预估还能生成的子串数量,并累加到总子串数量中。
解题关键
-
字符串替换与统计技巧:
- 通过巧妙地使用字符串替换操作(如
replaceAll和多次replace方法的调用),将不同形式的目标子串相关部分替换为特定标识字符串,从而方便后续统计数量。例如把"UCC"替换为"AAA"、"UC"和"CC"替换为"BBB",这种方式避免了复杂的字符遍历和匹配逻辑,能够简洁地统计出各种相关部分的数量情况。
- 通过巧妙地使用字符串替换操作(如
-
编辑距离的合理消耗与判断:
- 依据不同的字符串部分(已有的
"UCC"、可转换为"UCC"的"UC"与"CC"、单独的'U'和'C')按照其操作成本(消耗编辑距离的数量)来逐步消耗给定的编辑距离m,并且在每一步关键操作后及时判断m的剩余情况,以此决定后续的处理逻辑以及最终返回的结果。这要求对编辑距离的概念以及其在不同操作场景下的消耗计算非常清晰,确保不会超出给定的限制,同时尽可能充分利用编辑距离来生成更多的目标子串。
- 依据不同的字符串部分(已有的
-
边界情况处理:
- 在代码中多处涉及到对编辑距离
m用完(小于等于0)情况的处理,以及在考虑单独字符操作消耗编辑距离后,对于剩余编辑距离为奇数等边界情况的特殊处理(如(m - 1) / 2这样的计算),保证了程序在各种输入情况下都能正确返回符合逻辑的结果,避免出现数组越界、计算错误等异常情况。
- 在代码中多处涉及到对编辑距离
解题代码
public class Main {
public static int countString(String s, String sub) {
return (s.length() - s.replace(sub, "").length()) / sub.length();
}
public static int solution(int m, String s) {
// 第一步:先将字符串中的 "UCC" 替换为 "AAA",方便后续统计 "UCC" 的数量(以 "AAA" 的数量来间接统计)
String s1 = s.replaceAll("UCC", "AAA");
int uccCount = countString(s1, "AAA");
// 第二步:将 "UC" 和 "CC" 都替换为 "BBB",目的是统计通过替换这些部分能产生的变化数量
String s2 = s1.replace("UC", "BBB").replace("CC", "BBB");
int combinedCount = countString(s2, "BBB");
// 根据 "BBB" 的数量来消耗编辑距离,并更新总的子串数量(这里把 "UC" 和 "CC" 的替换视为一种贡献)
m -= combinedCount;
uccCount += combinedCount;
// 如果编辑距离已经用完或者不够了,直接返回相应结果
if (m <= 0) {
return uccCount + m;
}
// 统计剩余字符串中单独的 'U' 和 'C' 的数量,因为它们后续也可以通过操作来组成目标子串
int singleCharCount = countString(s2, "U") + countString(s2, "C");
// 每次操作单个字符算两次编辑操作(比如插入或替换),消耗编辑距离,并更新总的子串数量
m -= singleCharCount * 2;
uccCount += singleCharCount;
if (m <= 0) {
return uccCount + (m - 1) / 2;
}
// 如果还有剩余编辑距离,按照每次编辑距离能生成一个目标子串(这里假设可以合理利用剩余距离生成)来计算
return uccCount + m / 3;
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(10, "CUUUCC") == 5);
System.out.println(solution(6, "U") == 2);
System.out.println(solution(2, "UCCUUU") == 2);
}
}
总结
代码可读性提升方面
- AI帮你对原始代码添加了详细且清晰的注释,解释了每个方法的功能以及关键代码片段的目的,让代码逻辑更加直观明了。例如,详细说明了
countString方法是如何通过字符串长度的变化来统计特定子串出现次数的,还有solution方法里每一步操作(像替换不同字符串、消耗编辑距离、更新子串数量等)的意图所在,方便你后续回顾和理解代码,也有助于他人阅读你的代码来把握整体解题思路。 - 通过合理提取方法、优化变量命名等操作,进一步梳理了代码结构。原本可能比较晦涩难懂的代码经过整理后,各个功能模块区分更明确,代码的层次感更强,这样你在分析和调试代码时就能更快速地定位到关键部分,有助于排查可能出现的错误或者对代码进一步改进完善。
解题思路与关键分析方面
- 详细剖析了解题思路,将整个解题过程拆解成多个清晰的步骤。从最开始统计初始字符串中已有的
"UCC"子串数量,到考虑通过"UC"和"CC"生成"UCC"的情况,再到分析单独'U'和'C'字符的处理方式,以及最后利用剩余编辑距离生成子串,每一步都阐述了为什么要这样做以及具体如何操作,让你能从整体上把握如何根据编辑距离限制去尽可能多地生成目标子串,构建起完整的解题框架。 - 指出了解题的关键所在,比如强调了字符串替换与统计技巧的运用,像巧妙利用字符串替换操作来间接统计不同相关部分的数量;还着重说明了编辑距离合理消耗与判断的重要性,以及对边界情况处理的必要性等,让你明白在解题过程中需要重点关注哪些环节,避免陷入一些常见的逻辑误区,有助于你更精准地实现代码逻辑,保证程序在各种输入条件下都能正确运行。
数学原理梳理方面
- 梳理出了题目涉及的具体数学公式,像用于统计子串出现次数的公式,以及在不同阶段处理编辑距离消耗和子串数量更新的相关公式,通过公式的呈现,能让你更直观地看到代码背后的数学计算依据,便于理解代码中各种运算操作的合理性。
- 总结了相应的数学思想,例如分类讨论思想指导着对字符串不同部分的分别处理;等价转化思想体现为通过字符串替换将复杂问题转化为简单的数量统计和计算;逐步逼近思想反映在一步步利用编辑距离去逼近所能生成的最多目标子串数量的最优解过程中。明晰这些数学思想有助于你站在更高的角度去理解整个解题过程,并且可以举一反三,将类似的思想运用到其他相关题目中去。