问题描述
小S有一个由字符 'U' 和 'C' 组成的字符串 S,并希望在编辑距离不超过给定值 M 的条件下,尽可能多地在字符串中找到 "UCC" 子串。
编辑距离定义为将字符串 S 转化为其他字符串时所需的最少编辑操作次数。允许的每次编辑操作是插入、删除或替换单个字符。你需要计算在给定的编辑距离限制 M 下,能够包含最多 "UCC" 子串的字符串可能包含多少个这样的子串。
例如,对于字符串"UCUUCCCCC"和编辑距离限制m = 3,可以通过编辑字符串生成最多包含3个"UCC"子串的序列。
约束条件:
- 字符串长度不超过1000
测试样例
样例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" 子串。
题解
function solution(m, s) {
const n = s.length;
const dp = Array.from({ length: n + 1 }, () => Array(m + 1).fill(-1));
dp[0][0] = 0;
const matchInfo = Array.from({ length: n }, () => []);
for (let i = 0; i < n; i++) {
const matchLen = Math.min(m + 3, n - i);
const dpMatch = Array.from({ length: 4 }, () => Array(matchLen + 1).fill(Infinity));
dpMatch[0][0] = 0;
for (let p = 0; p < 4; p++) {
for (let q = 0; q < matchLen + 1; q++) {
if (dpMatch[p][q] > m) {
continue;
}
if (p < 3 && q < matchLen) { // 替换或保留
const cost = s[i + q] === 'UCC'[p] ? 0 : 1;
dpMatch[p + 1][q + 1] = Math.min(dpMatch[p + 1][q + 1], dpMatch[p][q] + cost);
}
if (p < 3) { // 插入
dpMatch[p + 1][q] = Math.min(dpMatch[p + 1][q], dpMatch[p][q] + 1);
}
if (q < matchLen) { // 删除
dpMatch[p][q + 1] = Math.min(dpMatch[p][q + 1], dpMatch[p][q] + 1);
}
}
}
for (let q = 0; q < matchLen + 1; q++) {
const c = dpMatch[3][q];
matchInfo[i].push([c, q]);
}
}
for (let p = 0; p < n + 1; p++) {
for (let q = 0; q < m + 1; q++) {
if (dp[p][q] === -1) {
continue;
}
if (p < n) { // 不尝试改动,直接跳过或者删除
dp[p + 1][q] = Math.max(dp[p][q], dp[p + 1][q]);
if (q <= m - 1) { // 删除
dp[p + 1][q + 1] = Math.max(dp[p][q], dp[p + 1][q + 1]);
}
}
if (p < n && matchInfo[p].length > 0) { // 匹配
for (const [c, l] of matchInfo[p]) {
if (c + q <= m && l + p <= n) {
dp[l + p][c + q] = Math.max(dp[l + p][c + q], dp[p][q] + 1);
}
}
}
}
}
let res = 0;
for (let i = 0; i < m + 1; i++) {
res = Math.max(res, dp[n][i]);
}
return res;
}
function main() {
console.log(solution(3, "UCUUCCCCC") === 3);
console.log(solution(6, "U") === 2);
console.log(solution(2, "UCCUUU") === 2);
}
main();
思路说明
题目要求在给定的编辑距离限制 m 下,尽可能多地找到字符串 S 中的 "UCC" 子串。我们可以通过将字符串 S 分割成多个部分,每个部分之间原本是 "UCC" 子串的位置,然后计算在这些部分中通过编辑操作可以增加的 "UCC" 子串数量。核心思想是通过贪心策略,优先考虑在每个部分中通过替换操作增加 "UCC" 子串,然后再考虑插入操作。
解题过程
-
分割字符串:首先将字符串 S 按照
"UCC"进行分割,得到一个列表ls,列表的长度减去 1 即为初始的"UCC"子串数量ans。 -
计算替换操作:遍历每个分割后的部分,统计可以通过替换操作增加的
"UCC"子串数量c1。 -
计算插入操作:统计可以通过插入操作增加的
"UCC"子串数量c2。 -
贪心策略:
- 首先使用替换操作增加
"UCC"子串,直到达到编辑距离限制 m 或替换操作用完。 - 然后使用插入操作增加
"UCC"子串,直到达到编辑距离限制 m 或插入操作用完。 - 最后,如果还有剩余的编辑距离,可以通过插入操作增加更多的
"UCC"子串。
- 首先使用替换操作增加
-
返回结果:最终的
"UCC"子串数量即为ans。
复杂度分析
- 时间复杂度:O(n),其中 n 是字符串 S 的长度。我们只需要遍历字符串一次进行分割和统计操作。
- 空间复杂度:O(n),主要用于存储分割后的字符串列表
ls。
知识点扩展
- 字符串处理:字符串的分割、遍历和统计是解决该题的基础。
- 贪心算法:通过贪心策略优先使用替换操作增加
"UCC"子串,然后再使用插入操作,是一种高效的解决问题的方法。 - 编辑距离:理解编辑距离的概念和计算方法,对于解决这类问题非常重要。