最大UCC子串运算 | 豆包MarsCode AI刷题

43 阅读5分钟

问题描述

小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" 子串。

下面为题目分析与解答

问题分析

本题的目标是通过最多 k 次相邻字符交换操作,使得字符串 s 的字典序最小。以下是问题的关键点:

  1. 交换操作的限制:每次只能交换相邻字符,最多可以执行 k 次。
  2. 字典序:字符串的字典序越小,意味着前面的字符越小(例如 '0' 比 '1' 小)。
  3. 贪心思想:从左到右尝试将较小的字符移到前面,但需要注意交换的次数不能超过 k。

解题思路

我们使用贪心策略解决该问题:

  1. 遍历字符串 s 的每个字符。
  2. 每次尝试将当前字符尽量往左移动,使得字典序更小。
  3. 在移动过程中消耗交换次数 k,如果 k 不足以完成移动,则只能移动部分。
  4. 如果 k 用完,直接输出结果。

具体步骤

  1. 选择最小字符

    • 每次选择当前位置开始的某个最小字符,尽量将它移动到当前可移动的最左侧。
  2. 消耗交换次数

    • 将字符从索引 j 移到 i 需要 j - i 次交换,如果 k 不足,则移动部分,最终 kkk 减少。
  3. 贪心判断

    • 保证当前能够构造的前缀是字典序最小的。

代码实现

def solution(n: int, k: int, s: str) -> str:
    s = list(s)  # 转为列表以便操作
    for i in range(n):
        if k <= 0:
            break  # 如果没有剩余操作,直接退出
        # 找到当前可移动范围内的最小字符
        min_pos = i
        for j in range(i + 1, min(i + k + 1, n)):
            if s[j] < s[min_pos]:
                min_pos = j
        # 如果最小字符就在当前位置,无需操作
        if min_pos == i:
            continue
        # 将最小字符移动到当前位置
        for j in range(min_pos, i, -1):
            s[j], s[j - 1] = s[j - 1], s[j]
        # 更新剩余的交换次数
        k -= (min_pos - i)
    return ''.join(s)


# 测试用例
if __name__ == "__main__":
    print(solution(5, 2, "01010"))  # 输出: '00101'
    print(solution(7, 3, "1101001"))  # 输出: '0110101'
    print(solution(4, 1, "1001"))  # 输出: '0101'

代码详细分析

核心部分

  1. for j in range(i + 1, min(i + k + 1, n))

    • 找到从当前索引 i 开始,能通过 k 次交换到达的范围内最小字符。
    • 如果当前位置已经是最小字符,直接跳过。
  2. for j in range(min_pos, i, -1)

    • 将找到的最小字符逐步向左移动,通过逐次交换调整顺序。
    • 每次移动会消耗 1 次交换。
  3. k -= (min_pos - i)

    • 减去移动最小字符消耗的交换次数。
  4. 提前退出

    • 当 k≤0 时,直接退出循环,因为已经没有足够的交换次数。

时间复杂度

  • 外层循环遍历字符串:O(n)。
  • 内层查找范围内最小字符:每次最多需要 O(k)。
  • 移动字符需要 O(k)。
  • 总复杂度:O(n⋅k)。

空间复杂度

  • 因为操作的是字符串的列表,没有使用额外空间:O(n)。

测试用例解析

测试用例 1

输入:n=5,k=2,s="01010"

  • 初始字符串01010
  • 第 1 步:在索引 i=0 开始,可在范围 [0, 2] 内找到最小字符 '0',无需移动。
  • 第 2 步:在索引 i=1 开始,可在范围 [1, 3] 内找到最小字符 '0',移动 1 步。
  • 结果00101

测试用例 2

输入:n=7,k=3,s="1101001"

  • 初始字符串1101001
  • 第 1 步:在索引 i=0 开始,找到范围 [0, 3] 内最小字符 '0',移动到索引 0,消耗 2 次操作。
  • 第 2 步:在索引 i=1 开始,找到范围 [1, 2] 内最小字符 '1',无需移动。
  • 第 3 步:在索引 i=2 开始,找到范围 [2, 3] 内最小字符 '0',移动到索引 2,消耗 1 次操作。
  • 结果0110101

测试用例 3

输入:n=4,k=1,s="1001"

  • 初始字符串1001
  • 第 1 步:在索引 i=0 开始,找到范围 [0, 1] 内最小字符 '0',移动到索引 0,消耗 1 次操作。
  • 结果0101

总结

  • 本算法利用贪心策略,逐步寻找最优解。
  • 对于每个字符,尽量选择当前范围内的最小字符,移动到目标位置。
  • 时间复杂度为 O(n⋅k),适用于一般的输入规模。