最大UCC子串计算(javascript版)-豆包marscode算法刷题

96 阅读4分钟

问题描述

小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" 子串,然后再考虑插入操作。

解题过程

  1. 分割字符串:首先将字符串 S 按照 "UCC" 进行分割,得到一个列表 ls,列表的长度减去 1 即为初始的 "UCC" 子串数量 ans

  2. 计算替换操作:遍历每个分割后的部分,统计可以通过替换操作增加的 "UCC" 子串数量 c1

  3. 计算插入操作:统计可以通过插入操作增加的 "UCC" 子串数量 c2

  4. 贪心策略

    • 首先使用替换操作增加 "UCC" 子串,直到达到编辑距离限制 m 或替换操作用完。
    • 然后使用插入操作增加 "UCC" 子串,直到达到编辑距离限制 m 或插入操作用完。
    • 最后,如果还有剩余的编辑距离,可以通过插入操作增加更多的 "UCC" 子串。
  5. 返回结果:最终的 "UCC" 子串数量即为 ans

复杂度分析

  • 时间复杂度:O(n),其中 n 是字符串 S 的长度。我们只需要遍历字符串一次进行分割和统计操作。
  • 空间复杂度:O(n),主要用于存储分割后的字符串列表 ls

知识点扩展

  • 字符串处理:字符串的分割、遍历和统计是解决该题的基础。
  • 贪心算法:通过贪心策略优先使用替换操作增加 "UCC" 子串,然后再使用插入操作,是一种高效的解决问题的方法。
  • 编辑距离:理解编辑距离的概念和计算方法,对于解决这类问题非常重要。