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

45 阅读4分钟
题目解析:解决“最小替换子串长度”问题

思路分析

问题理解

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

数据结构选择

  1. 计数数组:用于统计每个字符的出现次数。
  2. 滑动窗口:用于找到最小的子串,使得该子串中包含足够多的需要替换的字符。

解题步骤

  1. 统计字符出现次数:遍历字符串,统计每个字符的出现次数。

  2. 计算需要替换的次数:对于每个字符,计算其超出目标次数的部分。

  3. 滑动窗口

    • 初始化左右指针leftright,以及一个用于记录当前窗口内字符出现次数的数组windowCount
    • 扩展窗口:右指针向右移动,更新windowCount
    • 检查当前窗口是否满足替换条件:如果当前窗口内的字符出现次数满足替换条件,记录当前窗口长度,并尝试收缩窗口(左指针向右移动)。
    • 重复上述步骤,直到右指针遍历完整个字符串。
  4. 返回结果:返回找到的最小子串长度。

图解

  • 滑动窗口

    1. 统计字符出现次数:首先统计字符串中每个字符的出现次数。
    2. 计算需要替换的次数:计算每个字符超出目标次数的部分。
    3. 滑动窗口:使用滑动窗口技术来找到最小的子串长度,使得该子串中包含足够多的需要替换的字符。
  • 前缀和 + 哈希表

    1. 前缀和:计算每个位置的前缀和,记录每个字符的出现次数。
    2. 哈希表:使用哈希表记录每个前缀和的状态,找到最小的子串长度。

代码详解(使用滑动窗口):

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[] excess = new int[4];
    for (int i = 0; i < 4; i++) {
        excess[i] = Math.max(0, count[i] - targetCount);
    }

    // 如果所有字符都已经满足条件,直接返回0
    if (excess[0] == 0 && excess[1] == 0 && excess[2] == 0 && excess[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]++;
        right++;

        // 检查当前窗口是否满足替换条件
        while (left < right && windowCount[0] >= excess[0] && windowCount[1] >= excess[1] && windowCount[2] >= excess[2] && windowCount[3] >= excess[3]) {
            minLen = Math.min(minLen, right - left);

            // 收缩窗口
            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++;
        }
    }

    return minLen;
}

数据结构

  • 数组:用于统计字符的出现次数和记录当前窗口内字符的出现次数。
  • 哈希表:用于记录每个前缀和状态的最早出现位置。
  • 滑动窗口:用于在O(n)的时间复杂度内找到最小的子串长度。
  • 前缀和:用于记录每个位置的前缀和状态。

时间复杂度分析

  • 滑动窗口方法:时间复杂度为O(n)。
  • 前缀和 + 哈希表方法:时间复杂度为O(n)。

问题分解与抽象

  1. 统计字符出现次数

    • 遍历字符串,统计每个字符的出现次数。
    • 使用一个数组来存储每个字符的出现次数。
    • 输入:字符串input
    • 输出:一个数组count,表示每个字符的出现次数。
  2. 计算需要替换的次数

    • 计算每个字符超出目标次数的部分。
    • 使用一个数组来存储每个字符需要替换的次数。
    • 输入:数组count,目标出现次数targetCount
    • 输出:一个数组excess,表示每个字符需要替换的次数。
  3. 滑动窗口

    • 使用滑动窗口技术来找到最小的子串长度,使得该子串中包含足够多的需要替换的字符。
    • 维护一个窗口,扩展右指针并检查当前窗口是否满足替换条件,如果满足则收缩左指针。
    • 输入:字符串input,数组excess
    • 输出:最小的子串长度minLen
  4. 前缀和 + 哈希表

    • 计算每个位置的前缀和状态。
    • 使用哈希表记录每个前缀和状态的最早出现位置。
    • 通过前缀和状态的变化,找到最小的子串长度。
    • 输入:字符串input,数组excess
    • 输出:最小的子串长度minLen

通过将问题分解为多个子问题,并抽象出每个子问题的输入和输出,我们可以更清晰地理解问题的结构和解决方法。滑动窗口和前缀和 + 哈希表是两种不同的方法,但它们都通过分解和抽象问题来找到最优解。