最小替换子串长度

75 阅读3分钟

问题描述

小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. 计算目标频次:因为字符串长度是 4 的倍数,所以每个字符的目标频次是 targetCount = n / 4

  2. 初始化频次计数:遍历整个字符串,计算每个字符的实际出现频次。如果所有字符的频次都小于或等于 targetCount,说明无需替换字符,直接返回 0。

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

    • 通过滑动窗口(使用双指针 leftright)找到包含多余字符的最小子串。
    • 将窗口右边界(right)向右扩展,并减少当前字符的计数。
    • 检查窗口内的字符频次是否满足条件,即每种字符的频次都小于或等于 targetCount
    • 当满足条件时,记录窗口长度并尝试缩小窗口左边界(left),直到不再满足条件。
  4. 返回结果:输出所有满足条件的窗口中最小的长度。

举例

假设输入字符串为 "ASAFASAFADDD",目标是让每个字符 ASDF 出现的频次为 3

  1. 初始化:计算各字符初始频次,得到 A: 4, S: 3, D: 3, F: 2

  2. 滑动窗口操作

    • left = 0right = 0 开始,扩大右边界,计算字符频次。
    • 一旦窗口内的字符频次满足 targetCount,即 A: 3S: 3D: 3F: 3,记录窗口长度并缩小左边界。
  3. 窗口操作

    • right = 0 -> 3"ASAF" 频次不满足。
    • right = 4 -> 5:窗口达到 3 后满足条件,记录长度。
import java.util.*;;
public class Main {
    public static int solution(String input) {
        int n = input.length();
        int targetCount = n / 4;  // 每个字符的目标频次

        // 计算初始频次
        Map<Character, Integer> countMap = new HashMap<>();
        for (char ch : input.toCharArray()) {
            countMap.put(ch, countMap.getOrDefault(ch, 0) + 1);
        }

        // 检查是否已经满足条件
        if (countMap.getOrDefault('A', 0) <= targetCount &&
            countMap.getOrDefault('S', 0) <= targetCount &&
            countMap.getOrDefault('D', 0) <= targetCount &&
            countMap.getOrDefault('F', 0) <= targetCount) {
            return 0;
        }

        int minLength = n;
        int left = 0;

        // 滑动窗口
        for (int right = 0; right < n; right++) {
            countMap.put(input.charAt(right), countMap.get(input.charAt(right)) - 1);

            // 窗口内字符满足条件时,尝试缩小窗口
            while (countMap.getOrDefault('A', 0) <= targetCount &&
                   countMap.getOrDefault('S', 0) <= targetCount &&
                   countMap.getOrDefault('D', 0) <= targetCount &&
                   countMap.getOrDefault('F', 0) <= targetCount) {
                minLength = Math.min(minLength, right - left + 1);
                countMap.put(input.charAt(left), countMap.get(input.charAt(left)) + 1);
                left++;
            }
        }

        return minLength;
    }

    public static void main(String[] args) {
        //  You can add more test cases here
        System.out.println(solution("ADDF") == 1);
        System.out.println(solution("ASAFASAFADDD") == 3);
    }
}

代码详解

  • 目标频次初始化targetCount 表示每个字符的理想频次,为 n / 4

  • 初始频次计算countMap 用于记录初始字符串中 ASDF 的出现频次。

  • 滑动窗口

    • 遍历字符串,逐步扩大窗口的右边界。
    • 每次扩大右边界时,减少该字符的频次,以便保持当前窗口频次更新。
    • 当窗口内的字符频次满足 targetCount 时,更新最小窗口长度 minLength,并尝试缩小左边界。
  • 时间复杂度:由于滑动窗口只需遍历字符串一次,因此时间复杂度为 O(n)