刷题实践:最小替换子串长度 | 豆包MarsCode AI 刷题

32 阅读4分钟

问题描述

小F得到了一个特殊的字符串,这个字符串只包含字符ASDF,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得ASDF这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。


测试样例

样例1:

输入:input = "ADDF"
输出:1

样例2:

输入:input = "ASAFASAFADDD"
输出:3

样例3:

输入:input = "SSDDFFFFAAAS"
输出:1

样例4:

输入:input = "AAAASSSSDDDDFFFF"
输出:0

样例5:

输入:input = "AAAADDDDAAAASSSS"
输出:4

解题方法:滑动窗口

滑动窗口是一种双指针技巧,通过动态维护一个区间(窗口),用来高效地解决一类与区间或子数组有关的问题。其特点是:

  1. 窗口定义:一个连续子数组或子串。
  2. 指针移动:通常用两个指针 leftright 分别表示窗口的起点和终点,动态调整这两个指针的值以扩大或缩小窗口。
  3. 适用场景:在子数组或子串问题中,需要动态检查一个区间是否满足某些条件,并且在满足条件时更新结果。

为什么滑动窗口可以应用于此题

  1. 问题性质

    • 本题要求找到一个最小的子串,使得移除该子串后,剩下的字符频次达到平衡(每种字符出现次数小于或等于 n // 4)。
    • 我们可以通过滑动窗口动态维护一个候选子串 [left, right],并在满足条件时更新结果。
  2. 滑动窗口在本题的作用

    • 扩大窗口:通过移动右指针 right 增加窗口的长度,尝试找到包含更多不需要字符的子串。
    • 收缩窗口:当移除窗口后剩余字符频次满足平衡条件时,通过移动左指针 left 收缩窗口,找到可能的更小子串。

算法步骤

1. 初始化

  1. 计算字符串长度 n
  2. 计算每种字符的目标频次 target = n // 4
  3. 统计整个字符串中每种字符的频次,存储在字典 freq 中。

2. 快速检查是否已经平衡

  1. 如果每种字符的频次都已经小于或等于 target,说明字符串无需任何替换,直接返回结果 0

3. 初始化滑动窗口变量

  1. 定义两个指针 leftright,分别表示窗口的左边界和右边界。
  2. 定义 min_length,用于记录满足条件的最小子串长度,初始值为 n。

4. 移动右指针

  1. 使用 right 遍历整个字符串,表示动态扩大窗口的右边界。
  2. 每次移动右指针时,模拟移除窗口内的字符,将 freq[input[right]] 减去 1。

5. 检查平衡条件

  1. 在每次右指针更新后,检查剩余的字符频次是否满足条件,即所有字符的频次都小于或等于 target

    while all(f <= target for f in freq.values()):

6. 缩小窗口(左指针移动)

  1. 如果满足平衡条件,说明当前窗口是一个有效的候选子串。

  2. 更新最小子串长度

    • 计算当前窗口长度为 right - left + 1,并更新 min_length
  3. 尝试缩小窗口

    • 将左指针的字符重新加入到 freq 中(频次加 1)。
    • 移动左指针 left,缩小窗口。

7. 返回结果

  1. 当右指针遍历结束后,返回记录的 min_length,即满足条件的最小子串长度。

代码

def solution(input):
    n = len(input)
    target = n // 4  # 目标频次

    # 统计频次
    freq = {'A': 0, 'S': 0, 'D': 0, 'F': 0}
    for c in input:
        freq[c] += 1

    # 如果已经平衡,不需要替换任何字符
    if all(f <= target for f in freq.values()):
        return 0

    # 滑动窗口
    left = 0
    min_length = n  # 初始化为字符串长度
    for right in range(n):
        # 移动右指针,减去当前字符的频次
        freq[input[right]] -= 1

        # 检查是否满足平衡条件
        while all(f <= target for f in freq.values()):
            # 更新最小长度
            min_length = min(min_length, right - left + 1)
            # 收缩窗口:移动左指针
            freq[input[left]] += 1
            left += 1

    return min_length
    
if __name__ == "__main__":
    #  You can add more test cases here
    print(solution("ADDF") == 1 )
    print(solution("ASAFASAFADDD") == 3)