青训营X豆包MarsCode技术训练营第五课 | 豆包MarsCode AI刷题

110 阅读8分钟

方向一:学习方法与心得

一、题目展示 最小替换子串长度

问题描述

小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、题目理解

本题给定一个只包含字符 ASDF 且长度总是 4 的倍数的特殊字符串。要求通过尽可能少的字符替换操作,使得这四个字符在字符串中出现的频次相等,进而求出满足该条件的最小子串长度。

2、思路分析

  • 目标频次确定

    • 由于字符串长度是 4 的倍数,那么最终要达到的目标是让 ASDF 每个字符出现的频次都为字符串总长度除以 4 。例如,字符串长度为 8,那么每个字符最终应出现 2 次;长度为 12,则每个字符应出现 3 次,以此类推。
  • 滑动窗口思路

    • 可以考虑使用滑动窗口的方法来解决此问题。通过不断调整窗口的左右边界,来找到满足条件的最小子串。
    • 从左到右遍历字符串,随着窗口右边界的右移,不断更新窗口内各个字符的出现频次统计。
    • 当窗口内四个字符的出现频次满足一定条件(在目标频次到目标频次 + 1 之间,即既不能太少使得整体达不到目标频次,也不能太多超过目标频次太多)时,就有可能是一个满足要求的子串,此时记录下子串长度并尝试通过移动左边界来进一步缩小子串长度,看是否还能找到更短的满足条件的子串。

3、示例分析

  • 样例 1

    • 输入:input = "ADDF",字符串长度为 4,目标频次是每个字符出现 1 次。当前字符串中 A 出现 1 次,D 出现 2 次,F 出现 1 次,S 出现 0 次。通过将其中一个 D 替换为 S 即可满足条件,最小子串长度就是整个字符串长度 4,而只需要替换 1 个字符就能达到目标,所以输出 1 。
  • 样例 2

    • 输入:input = "ASAFASAFADDD",长度为 12,目标频次是每个字符出现 3 次。通过分析可以发现子串 "FAD"(或者其他等价的最小满足条件的子串)经过适当替换可以使整体满足要求,其长度为 3,所以输出 3 。
  • 样例 3

    • 输入:input = "SSDDFFFFAAAS",长度为 12,目标频次是每个字符出现 3 次。这里将 S 中的一个替换为 A 即可满足条件,整个字符串就是满足经过最少替换达到目标的情况,所以最小子串长度就是 1,输出 1 。
  • 样例 4

    • 输入:input = "AAAASSSSDDDDFFFF",长度为 16,目标频次是每个字符出现 4 次。该字符串已经满足四个字符出现频次相等的条件,无需进行任何替换,所以最小子串长度为 0,输出 0 。
  • 样例 5

    • 输入:input = "AAAADDDDAAAASSSS",长度为 16,目标频次是每个字符出现 4 次。可以发现子串 "AAAADDDD"(或者其他等价的最小满足条件的子串)经过适当替换可以使整体满足要求,其长度为 4,所以输出 4 。

三、代码展示

def solution(input):
    # 统计字符频次
    count_A = input.count('A')
    count_S = input.count('S')
    count_D = input.count('D')
    count_F = input.count('F')
    
    # 计算目标频次
    target_count = len(input) // 4
    
    # 计算需要替换的字符数
    excess_A = max(0, count_A - target_count)
    excess_S = max(0, count_S - target_count)
    excess_D = max(0, count_D - target_count)
    excess_F = max(0, count_F - target_count)
    
    # 如果所有字符都已经达到目标频次,直接返回0
    if excess_A == 0 and excess_S == 0 and excess_D == 0 and excess_F == 0:
        return 0
    
    # 滑动窗口初始化
    left = 0
    min_length = len(input)
    current_excess = {'A': 0, 'S': 0, 'D': 0, 'F': 0}
    
    # 滑动窗口遍历
    for right in range(len(input)):
        # 更新当前窗口内的字符频次
        current_excess[input[right]] += 1
        
        # 检查当前窗口是否满足替换条件
        while left <= right and current_excess['A'] >= excess_A and current_excess['S'] >= excess_S and current_excess['D'] >= excess_D and current_excess['F'] >= excess_F:
            # 更新最小子串长度
            min_length = min(min_length, right - left + 1)
            
            # 移动左指针
            current_excess[input[left]] -= 1
            left += 1
    
    return min_length

if __name__ == "__main__":
    # 你可以添加更多测试用例
    print(solution("ADDF") == 1)
    print(solution("ASAFASAFADDD") == 3)

四、代码分析总结

1、整体功能概述

这段代码实现了在给定一个只包含字符 ASDF 且长度为 4 的倍数的字符串的情况下,通过滑动窗口的方法找到经过最少替换操作使得 ASDF 这四个字符在字符串中出现频次相等的最小子串长度的功能。

2、代码结构与逻辑分析

(1). 字符频次统计与目标频次计算(前几行代码)

  • 首先通过 count 方法分别统计输入字符串 input 中字符 ASDF 的出现频次,存储在对应的变量 count_Acount_Scount_Dcount_F 中。
  • 然后根据字符串的长度计算出每个字符最终应达到的目标频次 target_count,即字符串长度除以 4 。

(2). 计算需要替换的字符数(中间部分代码)

  • 针对每个字符,通过比较其当前频次与目标频次,计算出超出目标频次的数量,分别存储在 excess_Aexcess_Sexcess_Dexcess_F 变量中。例如,如果 count_A 大于 target_count,则 excess_A 为两者差值;若 count_A 小于等于 target_count,则 excess_A 为 0 。
  • 接着通过判断这四个 excess 变量是否都为 0 来确定是否所有字符都已经达到目标频次,如果是,则直接返回 0,表示无需进行任何替换操作。

(3). 滑动窗口实现(后半部分代码)

  • 初始化

    • 定义了滑动窗口的左边界 left 初始化为 0 。
    • 将最小子串长度 min_length 初始化为输入字符串的长度,这是因为在后续遍历过程中会不断尝试找到更小的满足条件的子串长度。
    • 创建了一个字典 current_excess 用于记录当前窗口内每个字符超出目标频次的数量,初始化为每个字符都是 0 。
  • 遍历过程

    • 通过一个 for 循环,以 right 作为窗口的右边界,从 0 开始逐步遍历输入字符串。在每次循环中:

      • 首先更新当前窗口内的字符频次,即将当前右边界指向的字符在 current_excess 字典中的计数加 1 。

      • 然后通过一个 while 循环检查当前窗口是否满足替换条件。判断条件是左边界 left 小于等于右边界 right,并且当前窗口内每个字符超出目标频次的数量(通过 current_excess 字典获取)都大于等于之前计算出的该字符需要替换的数量(通过 excess_Aexcess_Sexcess_Dexcess_F 获取)。

      • 当满足上述替换条件时:

        • 更新最小子串长度 min_length,取当前 min_length 和当前窗口长度(right - left + 1)中的最小值。
        • 移动左边界,即将当前左边界指向的字符在 current_excess 字典中的计数减 1,并将左边界 left 向右移动一位。

3、测试部分

在 if __name__ == "__main__" 语句块中,对函数 solution 进行了简单的测试,通过比较函数返回值与预期值是否相等来验证函数的正确性。这里只给出了两个测试用例,分别是 "ADDF" 和 "ASAFASAFADDD",但可以根据需要添加更多的测试用例来进一步全面验证函数的性能和正确性。

4、优点与可能的改进点

优点:

  • 代码逻辑清晰,通过逐步计算目标频次、需要替换的字符数以及利用滑动窗口来寻找最小子串长度,各个功能模块划分明确,易于理解。
  • 对特殊情况(即所有字符已经达到目标频次)进行了单独处理,提高了程序的运行效率,避免了不必要的后续计算。

可能的改进点:

  • 目前的测试用例相对较少,可以增加更多不同类型、不同长度的测试用例,以更全面地验证函数在各种情况下的正确性。
  • 在计算需要替换的字符数时,可以考虑使用 Counter 类(如果使用 Python 标准库)来更方便地统计字符频次,并且可以在后续滑动窗口遍历过程中也继续使用 Counter 类来更新和管理窗口内的字符频次,可能会使代码更加简洁和高效。