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

41 阅读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

二. 思路解析

2.1 问题理解

题目要求我们通过尽可能少的替换操作,使得字符串中字符 ASDF 的出现频次相等。字符串的长度总是4的倍数,因此每个字符的目标频次应该是 length / 4

2.2 数据结构选择

  1. 频率数组:我们使用一个长度为4的数组 freq 来记录字符 ASDF 的出现次数。
  2. 滑动窗口:为了找到最小的子串长度,我们使用滑动窗口技术。滑动窗口可以帮助我们在 O(n) 的时间复杂度内找到满足条件的最小子串。

三. 解题步骤

3.1 算法步骤

  1. 计算频率

    • 遍历输入字符串,统计每个字符的出现次数,并存储在 freq 数组中。
  2. 计算目标频率

    • 计算每个字符的目标频率 targetFreq = length / 4
  3. 计算需要替换的字符数量

    • 对于每个字符,计算其需要替换的数量 excess[i] = max(0, freq[i] - targetFreq)。如果某个字符的频率已经达到目标频率,则 excess[i] 为0。
  4. 滑动窗口

    • 使用两个指针 left 和 right 来表示滑动窗口的左右边界。
    • 初始化 left 和 right 为0,并初始化一个数组 windowCount 来记录当前窗口内每个字符的出现次数。
    • 扩展窗口:每次将 right 指针向右移动,并更新 windowCount
    • 检查窗口是否包含所有需要替换的字符:如果 windowCount 中每个字符的出现次数都大于等于 excess 中对应的值,说明当前窗口满足条件。
    • 收缩窗口:如果当前窗口满足条件,尝试将 left 指针向右移动,以找到更小的窗口。
    • 更新最小窗口长度 minLength
  5. 返回结果

    • 最终返回 minLength,即满足条件的最小子串长度。

3.2 复杂度分析

3.2.1 时间复杂度分析

  1. 频率计算

    • 遍历整个输入字符串一次,计算每个字符的频率。这一步的时间复杂度是 O(n),其中 n 是输入字符串的长度。
  2. 滑动窗口

    • 滑动窗口的 right 指针会遍历整个字符串一次,时间复杂度是 O(n)。
    • 在每次扩展窗口时,可能会进入一个内部循环来收缩窗口。但由于每个字符最多只会被 left 和 right 指针各访问一次,因此收缩窗口的总时间复杂度也是 O(n)。

综合以上两部分,整个算法的时间复杂度是 O(n)。

3.2.2空间复杂度

  1. 频率数组

    • 使用了一个长度为4的数组 freq 来存储字符频率,空间复杂度是 O(1)。
  2. 滑动窗口计数数组

    • 使用了一个长度为4的数组 windowCount 来存储当前窗口内字符的计数,空间复杂度是 O(1)。
  3. 其他变量

    • 使用了几个整数变量(如 targetFreqleftrightminLength 等),这些变量的空间复杂度都是 O(1)。

综合以上各部分,整个算法的空间复杂度是 O(1)。

四. Code

public class Main {
    public static int solution(String input) {
        // 计算每个字符的频率
        int[] freq = new int[4]; // 0: A, 1: S, 2: D, 3: F
        for (char c : input.toCharArray()) {
            switch (c) {
                case 'A': freq[0]++; break;
                case 'S': freq[1]++; break;
                case 'D': freq[2]++; break;
                case 'F': freq[3]++; break;
            }
        }

        // 计算目标频率
        int targetFreq = input.length() / 4;

        // 计算需要替换的字符数量
        int[] excess = new int[4];
        for (int i = 0; i < 4; i++) {
            excess[i] = Math.max(0, freq[i] - targetFreq);
        }

        // 如果所有字符的频率已经平衡,直接返回0
        if (excess[0] == 0 && excess[1] == 0 && excess[2] == 0 && excess[3] == 0) {
            return 0;
        }

        // 使用滑动窗口找到最小的子串长度
        int left = 0, right = 0;
        int minLength = input.length();
        int[] windowCount = new int[4];

        while (right < input.length()) {
            // 扩展窗口
            switch (input.charAt(right)) {
                case 'A': windowCount[0]++; break;
                case 'S': windowCount[1]++; break;
                case 'D': windowCount[2]++; break;
                case 'F': windowCount[3]++; break;
            }
            right++;

            // 检查窗口是否包含所有需要替换的字符
            while (windowCount[0] >= excess[0] && windowCount[1] >= excess[1] &&
                   windowCount[2] >= excess[2] && windowCount[3] >= excess[3]) {
                minLength = Math.min(minLength, right - left);

                // 收缩窗口
                switch (input.charAt(left)) {
                    case 'A': windowCount[0]--; break;
                    case 'S': windowCount[1]--; break;
                    case 'D': windowCount[2]--; break;
                    case 'F': windowCount[3]--; break;
                }
                left++;
            }
        }

        return minLength;
    }

    public static void main(String[] args) {
        // 你可以添加更多测试用例
        System.out.println(solution("ADDF") == 1);
        System.out.println(solution("ASAFASAFADDD") == 3);
    }
}

五. 总结

使用AI解题可以帮助开发者更高效、准确地解决问题,同时提供丰富的学习资源和指导,帮助开发者不断提升编程技能。通过与AI的互动,开发者可以更好地理解问题、优化代码,并逐步提升自己的编程能力。