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

48 阅读4分钟

最小化替换使字符串字符频次相等

问题描述

小F手中有一个特殊的字符串,这个字符串只包含字符 ASDF,并且长度总是 (4) 的倍数。任务是通过尽可能少的替换,使得这四个字符在字符串中出现的频次相等。

目标是求出使字符串字符频次相等所需替换的 最小子串长度


解题思路

核心目标

字符串要达到平衡状态,每个字符的频次都应等于 n / 4,其中 (n) 是字符串长度。如果某些字符的频次超过 n / 4,则需要通过替换部分字符来实现平衡。问题的本质可以转化为:找到字符串的一个最小子串,通过替换这个子串中的字符使得剩余部分达到平衡。

这一问题可以用滑动窗口算法高效解决,因为滑动窗口能够快速定位满足条件的最小区间,同时动态调整窗口大小。


详细步骤

1. 统计频次

我们首先统计字符串中每个字符的频次。如果所有字符的频次已经等于 n / 4,说明字符串已经是平衡的,直接返回结果为 (0)。否则,我们将继续寻找一个最小子串,使得替换该子串后,字符串可以达到平衡。

例如,对于字符串 "ASAFASAFADDD"

  • 总长度 (n = 12),目标频次为 n / 4 = 3
  • 频次统计结果:A = 4, S = 3, D = 3, F = 2
  • 可以发现字符 AF 的频次需要调整。
2. 滑动窗口

滑动窗口是解决这类问题的核心工具。它通过动态调整窗口的左右边界,逐步找到满足条件的最优解。

  • 初始化

    • 使用两个指针 leftright 表示窗口的左右边界,初始均为 (0)。
    • 在滑动窗口的过程中,右指针逐步扩展窗口,左指针则在窗口满足条件时尝试收缩窗口。
  • 窗口内字符调整

    • 每次右指针向右移动时,将窗口内字符的频次减少,同时检查窗口外的字符频次是否已经满足目标。
    • 如果窗口外的字符频次满足平衡条件,尝试记录窗口大小并收缩左指针以进一步优化窗口。
  • 收缩窗口

    • 左指针向右移动时,将窗口左端字符重新计入频次,同时保持窗口外部分满足目标。
3. 动态更新最小窗口

在滑动窗口的每次调整中,如果窗口外部分满足平衡条件,就更新最小窗口长度。最终,滑动窗口结束后,记录的最小窗口长度即为答案。


实现代码

以下是 Java 实现代码:

import java.util.HashMap;

public class Main {
    public static int solution(String input) {
        int n = input.length();
        int target = n / 4;
        
        // Frequency map to count occurrences of A, S, D, F
        HashMap<Character, Integer> count = new HashMap<>();
        count.put('A', 0);
        count.put('S', 0);
        count.put('D', 0);
        count.put('F', 0);

        // Count the frequency of each character in the input
        for (char ch : input.toCharArray()) {
            count.put(ch, count.get(ch) + 1);
        }

        // If the string is already balanced, no need to replace anything
        if (count.get('A') == target && count.get('S') == target &&
            count.get('D') == target && count.get('F') == target) {
            return 0;
        }

        // Sliding window approach to find the minimum window size
        int left = 0;
        int minLength = n;

        for (int right = 0; right < n; right++) {
            char chRight = input.charAt(right);
            count.put(chRight, count.get(chRight) - 1);

            // Check if the characters outside the window are already balanced
            while (left <= right && count.get('A') <= target &&
                   count.get('S') <= target && count.get('D') <= target &&
                   count.get('F') <= target) {
                // Update minimum window size
                minLength = Math.min(minLength, right - left + 1);

                // Shrink the window from the left
                char chLeft = input.charAt(left);
                count.put(chLeft, count.get(chLeft) + 1);
                left++;
            }
        }

        return minLength;
    }

    public static void main(String[] args) {
        // Test cases
        System.out.println(solution("ADDF") == 1);
        System.out.println(solution("ASAFASAFADDD") == 3);
        System.out.println(solution("SSDDFFFFAAAS") == 1);
    }
}

示例分析

示例 1

  • 输入input = "ADDF"

  • 过程

    1. 统计频次:A=1, S=0, D=2, F=1,目标为 n/4 = 1

    2. 使用滑动窗口找到最小子串:

      • 窗口 [3, 3] 包含 D,替换后频次平衡。
    3. 最小子串长度为 1

  • 输出1


示例 2

  • 输入input = "ASAFASAFADDD"

  • 过程

    1. 统计频次:A=4, S=3, D=3, F=2,目标为 n/4 = 3

    2. 使用滑动窗口找到最小子串:

      • 窗口 [8, 10] 包含 ADD,替换后频次平衡。
    3. 最小子串长度为 3

  • 输出3


示例 3

  • 输入input = "SSDDFFFFAAAS"

  • 过程

    1. 统计频次:A=3, S=3, D=2, F=4,目标为 n/4 = 3

    2. 使用滑动窗口找到最小子串:

      • 窗口 [8, 8] 包含 F,替换后频次平衡。
    3. 最小子串长度为 1

  • 输出1


时间与空间复杂度

时间复杂度

  • 统计频次:遍历字符串一次,复杂度为 O(n)O(n)O(n)。
  • 滑动窗口:左右指针各遍历字符串一次,复杂度为 O(n)O(n)O(n)。
  • 总时间复杂度为 O(n)O(n)O(n)。

空间复杂度

  • 使用了一个 HashMap 记录频次,空间复杂度为 O(1)O(1)O(1)(固定大小为 4)。

总结

通过滑动窗口算法,我们能够高效解决此类最小子串问题。关键点在于:

  1. 通过频次统计找到需要调整的字符
  2. 使用滑动窗口逐步缩小范围找到最优解