题目大意
给定一个由字符 'U' 和 'C' 组成的字符串 ,在不超过 次插入、删除或替换单个字符的条件下,使字符串中 "UCC" 子串的数量最多。
测试样例及解释
样例1:
输入:
m = 3,s = "UCUUCCCCC"
输出:3
可以将字符串修改为 "UCCUCCUCC"(2 次替换操作,不超过给定值 m = 3),包含 3 个 "UCC" 子串。
样例2:
输入:
m = 6,s = "U"
输出:2
可以将字符串修改为 "UCCUCC",(5 次插入操作,不超过给定值 m = 6),包含 2 个 "UCC" 子串。
样例3:
输入:
m = 2,s = "UCCUUU"
输出:2
可以将字符串修改为 "UCCUCC",(2 次替换操作,不超过给定值 m = 2),包含 2 个 "UCC" 子串。
解题思路
- 我们可以发现,删除、替换操作一定不会比插入操作更优秀:
这一点的核心在于尽可能保留更多的字符以备后续组合;由于目标子串长度仅为 3,这种贪心策略是可行的,不需要动态规划就可以完成。
举例来说,如果存在形如
“UCUC”的字符串,我们可以通过一次删除操作获得“UCC”,或是通过一次替换操作获得“UCCC”,但我们也可以通过一次插入操作获得“UCCUC”,在同样包含一个目标子串的同时保留了一个"UC",如果还有一次操作机会就可以变为“UCCUCC”,再次得到一个目标子串,而如果进行了删除操作或是替换操作则无法达成。
- 在确认只进行插入操作后,我们就需要通过尽可能少的操作使 内的所有字符均为某个
"UCC"的一部分,这一点也是可以通过贪心得到的,核心思想是尽可能少的破坏原有字符串,具体情况如下:
int ans=0,s1=0,s2=0;//分别记录答案数量,当前长度为1的子串数量,当前长度为2的子串数量
int i=0;
while(i<s.size()){//遍历整个字符串 S
if(s[i]=='U'){//如果当前以'U'开头
if(i+1>=s.size()||s[i+1]=='U'){//后续无字符或紧跟着一个'U',无法延伸
s1++;//记录长度为1的子串数量
i++;//当前子串为"U"结束,寻找下一个子串
}//此时需要两次插入操作获得一个目标子串"UCC"
else if(i+2>=s.size()||s[i+2]=='U'){//紧跟着一个'C',但后续无字符或紧跟着一个'U',无法延伸
s2++;//记录长度为2的子串数量
i+=2;//当前子串为"UC"结束,寻找下一个子串
}//此时需要一次插入操作获得一个目标子串"UCC"
else{//当前子串已经为"UCC"
ans++;//目标子串数量+1
i+=3;//当前子串为"UCC"结束,寻找下一个子串
}
}
else{//如果当前以'C'开头
if(i+1>=s.size()||s[i+1]=='U'){//后续无字符或紧跟着一个'U',无法延伸
s1++;//记录长度为1的子串数量
i++;//当前子串为"C"结束,寻找下一个子串
}//此时需要两次插入操作获得一个目标子串"UCC"
else{//紧跟着一个'C',可在开头插入一个'U'组成"UCC"
s2++;//记录长度为2的子串数量
i+=2;//当前子串为"CC"结束,寻找下一个子串
}//此时需要一次插入操作获得一个目标子串"UCC"
}
}
- 获得了当前长度为1、2的子串数量后,我们的策略便是优先补齐当前长度为2的子串,因为每一次操作都可以获得一个新的目标子串;在补齐所有当前长度为2的子串后再去补齐当前长度为1的子串,每两次次操作可以获得一个新的目标子串;如果在补齐所有当前长度为1的子串后还有次数剩余,我们就可以每三次获得一个新的目标子串,得到最终答案:
ans+=std::min(m,s2);//优先补齐当前长度为2的子串
m-=std::min(m,s2);//计算剩余操作次数
ans+=std::min(m/2,s1);//补齐当前长度为1的子串
m-=std::min(m/2,s1)*2;//计算剩余操作次数
ans+=m/3;//添加全新的目标子串
- 最后将 返回即可。