使用滑动窗口寻找《最小替换子串长度》 | 豆包MarsCode AI刷题

84 阅读4分钟

问题描述

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

测试样例

样例1:

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

样例2:

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

样例3:

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

易错点

在刚看到这个问题的时候,我错误的认为是要计算最少需要替换多少个字符,才能使得字符串中各字符的出现频次相同,形成“平衡字符串”。由于字符串长度为4的倍数,为达到目标每个字符的频次需要为partial=n4partial=\frac{n}{4},其中n为字符串长度,partial为期望得到的平衡字符频次。于是按照我的错误理解,我们只需要关注超过partial频次的字符,计算它们分别超出partial的数量并求和便可得出答案。但是按照题意,本题是要我们求出构建“平衡字符串”所需要替换的最小子串长度。需要通过替换子串使得各字符的出现次数均等,而不是直接修改单个字符。

问题分析

在上一部分中,我们提到在该题目中,需要对子串进行替换从而使得各字符分布平衡。因此,我们可以将字符串划分为两个区域,一个是需要替换的子串,另一个是不需要替换的部分。为了实现字符频次平衡的目的,在字符串不需要替换的部分中,各字符的频次一定小于等于partial。你可能会问,为什么必须要满足这一条件呢?这是因为,如果在不需要替换的部分各字符频次小于等于平衡频次,那么我们就可以通过调整需要替换子串中的字符,使得整个字符串的字符出现次数均等。如果在不需要替换的部分中,某些字符的频次已经超过partial了,那么无论我们怎么调整需要替换子串中的字符,都无法使得字符串中的字符频次相等。于是,我们很自然会想到枚举所有满足这一条件的子串,然后通过比较得出这些子串的最小长度。

代码实现

由于在该问题中,需要替换的子串为字符串中连续的区域,我们通过维护一个滑动窗口即可查找所有满足条件的子串。窗口左边界定义为left,而窗口右边界定义为right。当不需要替换区域中的字符频次小于或等于partial时,我们就缩短窗口,尝试寻找最短子串。而当不需要替换区域的字符频次大于partial时,我们就扩大窗口,尝试减少某些字符在不需要替换区域中的数量以达到条件。

于是在代码中,一开始将leftright都设为0,如果初始字符串就满足“平衡字符串”的条件,函数就返回0,不需要对进行字串替换。如果不满足条件,则将right指针向前移动,直到满足平衡条件。当满足条件时,就对窗口进行缩短,将left指针向前移动,同时记录当前的最短子串长度。具体代码实现如下:

def solution(input_str):
    from collections import Counter

    n = len(input_str)
    target_freq = n // 4
    char_count = Counter(input_str)
    min_length = n  # 最小子串长度,初始为字符串长度
    
    # 如果已经平衡,直接返回0
    if all(char_count[char] <= target_freq for char in "ASDF"):
        return 0
    
    # 滑动窗口
    left = 0
    for right, c in enumerate(input_str):
        char_count[c] -= 1
        
        # 当窗口外的字符频次满足要求时,尝试更新最小窗口长度
        while all(char_count[char] <= target_freq for char in "ASDF"):
            min_length = min(min_length, right - left + 1)
            char_count[input_str[left]] += 1
            left += 1
    
    return min_length

总结

在该题目中,我们将字符串分割为了两部分,一是需要替换的子串,二是不需要替换的部分,在后者中各字符的频次需要小于或等于partial。然后,我们选择了使用滑动窗口(或叫做双指针)寻找了所有满足条件的需要修改的子串,并比较它们的长度以得到最短子串长度。