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

64 阅读4分钟

问题描述

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

思路分析

  1. 问题本质

    • 需要找到一个最短的子串,通过替换这个子串中的字符,使得整个字符串中 ASDF 的频次相等。
    • 使用滑动窗口技术来解决这个问题。滑动窗口可以动态地调整窗口的大小,找到符合条件的最短子串。
  2. 解决方案

    • 统计字符频次:首先统计字符串中 ASDF 的频次。
    • 计算需要替换的数量:计算每个字符超出目标频次的数量。
    • 滑动窗口:使用两个指针 left 和 right 来维护一个滑动窗口,窗口内的字符频次必须满足 ASDF 的替换条件。
    • 动态调整窗口:不断扩大窗口(移动 right 指针),直到窗口内的字符频次满足条件。然后收缩窗口(移动 left 指针),找到最短的满足条件的窗口。
    • 返回结果:返回满足条件的最短子串长度。

代码详解


public class Main {
    public static int solution(String input) {
        int n = input.length();
        int targetCount = n / 4;
        
        // Count occurrences of each character
        int countA = 0, countS = 0, countD = 0, countF = 0;
        for (char c : input.toCharArray()) {
            switch (c) {
                case 'A': countA++; break;
                case 'S': countS++; break;
                case 'D': countD++; break;
                case 'F': countF++; break;
            }
        }
        
        // Calculate the number of replacements needed for each character
        int excessA = Math.max(0, countA - targetCount);
        int excessS = Math.max(0, countS - targetCount);
        int excessD = Math.max(0, countD - targetCount);
        int excessF = Math.max(0, countF - targetCount);
        
        // If all characters are already balanced, return 0
        if (excessA == 0 && excessS == 0 && excessD == 0 && excessF == 0) {
            return 0;
        }
        
        // Sliding window to find the minimum length substring
        int left = 0, right = 0;
        int minLength = n;
        int[] windowCount = new int[4]; // Count of A, S, D, F in the current window
        
        while (right < n) {
            // Expand the window
            char rightChar = input.charAt(right);
            switch (rightChar) {
                case 'A': windowCount[0]++; break;
                case 'S': windowCount[1]++; break;
                case 'D': windowCount[2]++; break;
                case 'F': windowCount[3]++; break;
            }
            right++;
            
            // Check if the current window satisfies the condition
            while (windowCount[0] >= excessA && windowCount[1] >= excessS && windowCount[2] >= excessD && windowCount[3] >= excessF) {
                minLength = Math.min(minLength, right - left);
                
                // Contract the window
                char leftChar = input.charAt(left);
                switch (leftChar) {
                    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) {
        //  You can add more test cases here
        System.out.println(solution("ADDF") == 1);
        System.out.println(solution("ASAFASAFADDD") == 3);
    }
}

知识总结

  1. 滑动窗口技术

    • 滑动窗口是一种动态调整窗口大小的算法,适用于寻找满足特定条件的最小子串或子数组。
    • 通过两个指针 left 和 right 来维护窗口的边界,逐步扩大和收缩窗口,找到最优解。
  2. 字符频次统计

    • 使用 switch 语句统计字符串中每个字符的频次,简单明了。
    • 计算每个字符超出目标频次的数量,确定需要替换的数量。
  3. 条件判断

    • 在滑动窗口过程中,需要不断检查窗口内的字符频次是否满足条件。
    • 使用 while 循环来不断收缩窗口,找到最短的满足条件的子串。
  4. 代码优化

    • 通过使用数组 windowCount 来记录窗口内每个字符的频次,可以提高代码的执行效率。
    • 在 main 函数中添加更多的测试用例,确保代码的正确性和鲁棒性。

个人理解与学习建议

  1. 理解滑动窗口

    • 滑动窗口是一种非常实用的算法技术,通过动态调整窗口的大小,可以高效地解决很多字符串和数组的问题。
    • 通过这个题目,可以加深对滑动窗口的理解,学会如何在实际问题中应用。
  2. 代码可读性

    • 使用有意义的变量名,如 targetCountexcessA 等,可以提高代码的可读性。
    • 添加注释,解释每一步的逻辑,有助于他人理解代码。
  3. 性能考虑

    • 滑动窗口的时间复杂度为 O(n)O(n),适用于中等规模的输入。
    • 注意优化字符频次统计和条件判断的过程,避免不必要的计算。
  4. 学习建议

    • 多做练习:通过多做类似的题目,可以提高对滑动窗口的理解和应用能力。
    • 思考边界情况:在编写代码时,思考各种边界情况,确保代码的鲁棒性。
    • 代码模块化:将代码分成多个函数或模块,可以提高代码的可维护性和可读性。