41 最小替换子串长度 题解 | 豆包MarsCode AI刷题

122 阅读5分钟

问题描述

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

测试样例

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

样例2: 输入:input = "ASAFASAFADDD" 输出:3

样例3: 输入:input = "SSDDFFFFAAAS" 输出:1

样例4: 输入:input = "AAAASSSSDDDDFFFF" 输出:0

样例5: 输入:input = "AAAADDDDAAAASSSS" 输出:4

问题分析

给定一个只包含字符 A、S、D 和 F 的字符串,我们的目标是通过最少的替换操作,使得这四个字符的出现频次在字符串中相等。根据题目描述,字符串的长度总是 4 的倍数,因此目标是每个字符的出现频次为 n / 4(其中 n 是字符串的长度)。

例如:

  • 如果字符串的长度为 12,目标是每个字符出现的次数为 12 / 4 = 3。
  • 如果字符串的长度为 16,目标是每个字符出现的次数为 16 / 4 = 4。

问题要求我们找到一个最小的子串,替换该子串中的字符,使得最终所有字符的频次相等。

解题思路

1.统计字符频率: 首先统计字符串中每个字符 A、S、D、F 的出现频次。目标是让每个字符的频次接近字符串总长度的四分之一,即 n / 4(其中 n 是字符串的长度)。

2.计算需要替换的字符数: 对于每个字符,我们计算它超出目标频次的部分,这部分字符需要被替换成其他字符。例如,若字符 A 的频次超过 n / 4,则多余的 A 需要被替换成其他字符。

3.滑动窗口: 使用滑动窗口技术来找到最小的子串长度,满足以下条件:该子串中包含足够数量的需要替换的字符。滑动窗口通过两个指针 left 和 right 来扩展和收缩窗口。窗口中的字符被检查是否可以通过替换来平衡字符频次。

4.判断是否满足条件: 在滑动窗口中,我们不断检查窗口内的字符是否能够替换成目标频次。如果满足条件,更新最小子串长度,并尝试通过移动 left 指针来缩小窗口,直到不满足条件。

5.输出结果: 如果没有需要替换的字符,直接返回 0。否则返回最小的子串长度。

详细步骤

1.初始化变量:

  • count[ ] 用来统计字符串中 A、S、D、F 的频次。
  • targetCount 为每个字符的目标频次,即 n / 4。
  • needReplace[ ] 存储每个字符需要替换的次数,即 max(0, count[i] - targetCount)。

2.滑动窗口:

  • 使用 left 和 right 两个指针初始化滑动窗口,windowCount[ ] 用来统计当前窗口中各个字符的频次。
  • 扩展窗口:移动 right 指针,每次扩展一个字符。
  • 收缩窗口:当窗口中的字符已经可以满足替换要求时,尝试移动 left 指针,缩小窗口并更新最小子串长度。

3.终止条件:

  • 若窗口中的字符已经足够替换(即窗口内有足够多的字符需要替换),更新最小子串长度,并尝试收缩窗口。

4.返回结果:

  • 最终返回最小的子串长度,或者如果所有字符的频次已经平衡,返回 0。

代码实现

    public static int solution(String input) {
        int n = input.length();
        int targetCount = n / 4;
        
        // 统计每个字符的初始频次
        int[] count = new int[4]; // 0: A, 1: S, 2: D, 3: F
        for (char c : input.toCharArray()) {
            if (c == 'A') count[0]++;
            else if (c == 'S') count[1]++;
            else if (c == 'D') count[2]++;
            else if (c == 'F') count[3]++;
        }
        
        // 计算每个字符需要替换的次数
        int[] needReplace = new int[4];
        for (int i = 0; i < 4; i++) {
            needReplace[i] = Math.max(0, count[i] - targetCount);
        }
        
        // 如果所有字符的频次已经相等,直接返回0
        if (needReplace[0] == 0 && needReplace[1] == 0 && needReplace[2] == 0 && needReplace[3] == 0) {
            return 0;
        }
        
        // 使用滑动窗口找到最小的替换子串长度
        int left = 0, right = 0;
        int minLen = n;
        int[] windowCount = new int[4];
        
        while (right < n) {
            char rightChar = input.charAt(right);
            if (rightChar == 'A') windowCount[0]++;
            else if (rightChar == 'S') windowCount[1]++;
            else if (rightChar == 'D') windowCount[2]++;
            else if (rightChar == 'F') windowCount[3]++;
            
            while (left <= right && windowCount[0] >= needReplace[0] && windowCount[1] >= needReplace[1] && windowCount[2] >= needReplace[2] && windowCount[3] >= needReplace[3]) {
                minLen = Math.min(minLen, right - left + 1);
                char leftChar = input.charAt(left);
                if (leftChar == 'A') windowCount[0]--;
                else if (leftChar == 'S') windowCount[1]--;
                else if (leftChar == 'D') windowCount[2]--;
                else if (leftChar == 'F') windowCount[3]--;
                left++;
            }
            right++;
        }
        
        return minLen;
    }
 
    public static void main(String[] args) {
        System.out.println(solution("ADDF") == 1);
        System.out.println(solution("ASAFASAFADDD") == 3);
        System.out.println(solution("SSDDFFFFAAAS") == 1);
        System.out.println(solution("AAAASSSSDDDDFFFF") == 0);
        System.out.println(solution("AAAADDDDAAAASSSS") == 4);
    }
}

解析:
  • count[ ] 数组: 用来存储每个字符出现的频次。
  • needReplace[ ] 数组: 存储每个字符需要替换的次数,若字符数量超过目标频次,则记录需要替换的数量。
  • 滑动窗口: left 和 right 指针用于动态维护一个窗口,当窗口内的字符数量足够时,更新最小长度并收缩窗口。

总结

这道题的核心在于通过 统计频次滑动窗口 技术,找到最小的子串并进行替换,以满足所有字符频次相等。通过合理使用滑动窗口和频次比较,我们能够有效地解决这个问题,找到最小的替换子串长度。