青训营X豆包MarsCode 技术训练营之最小替换子串长度 | 豆包MarsCode AI 刷题

89 阅读3分钟

问题描述

我们有一个特殊字符串 s,它只包含字符 ASDF,并且长度始终是 4 的倍数。任务是通过替换最少的字符,使得 ASDF 的频率在字符串中变得相等。换句话说,我们需要找到一个最小的子串,这个子串包含一些可以替换的字符。将这个子串替换后,整个字符串的每个字符频率变得相等。

例如,对于输入 ASAFASAFADDD,我们需要找到长度最小的子串,使得替换这个子串后可以达到 ASDF 每个字符出现次数相同。

解题思路

考虑到子串长度是固定的,要解决这个问题,我们可以使用滑动窗口(Sliding Window),这是一个经典的在一维数据结构中寻找最优子串的方法。这个方法的关键在于利用双指针在字符串中找出满足条件的最小区间。

步骤

  • 目标频率计算

    • 由于字符串 s 的长度总是 4 的倍数,所以每个字符在平衡状态下的频率应该是 len(s) / 4
    • 例如,对于 s = "AAAASSSSDDDDFFFF",长度为 16,每个字符 ASDF 目标频率就是 16 / 4 = 4
  • 初始化字符频率计数

    • 使用 Counter 统计字符串中 ASDF 的当前频率。
    • 如果统计的频率已经满足目标频率,则不需要替换任何字符,可以直接返回 0
  • 滑动窗口查找最小子串

    • 我们使用双指针(left 和 right)构成滑动窗口,在窗口内包含一些需要替换的字符。
    • 移动 right 指针逐个扩大窗口,并减少窗口内字符的频率。
    • 在窗口扩展的过程中,如果发现移除这个窗口后,字符串其余部分的每个字符的频率均不超过目标频率,则说明我们找到一个满足条件的子串。
    • 这时,我们尝试移动 left 指针缩小窗口,并不断更新最小子串长度 min_length
  • 输出结果

    • 滑动窗口结束后,返回 min_length,即可以使字符串平衡的最小替换子串的长度。

代码解释

  • Counter(s) :用来统计字符串中每个字符的初始频率。

  • if all(char_count[c] == target_count for c in "ASDF") :判断初始字符频率是否已经平衡,如果是,则返回 0

  • 滑动窗口:通过调整窗口的左右边界,不断尝试缩小满足条件的子串。

  • min_length:记录最小的满足条件的子串长度,最终返回最小值。

  • 时间复杂度O(n),其中 n 是字符串的长度。我们通过滑动窗口的方式遍历整个字符串,只需一遍遍历即可。

  • 空间复杂度O(1),仅需要一个定长的计数器来统计字符的频率。

完整代码

from collections import Counter

def min_replacement_substring_length(s):
    n = len(s)
    target_count = n // 4
    char_count = Counter(s)
    
    # 检查是否已经满足要求
    if all(char_count[c] == target_count for c in "ASDF"):
        return 0
    
    min_length = n  # 初始化为字符串的长度
    
    left = 0
    for right in range(n):
        char_count[s[right]] -= 1
        
        # 判断窗口 [left, right] 是否可以满足条件
        while all(char_count[c] <= target_count for c in "ASDF"):
            min_length = min(min_length, right - left + 1)
            char_count[s[left]] += 1
            left += 1

    return min_length