青训营X豆包MarsCode 刷题笔记 IV |豆包MarsCode AI刷题

66 阅读4分钟

替换最小子串长度问题刷题笔记


一、题目解析

问题描述
给定一个仅包含字符 ASDF 的字符串,其长度总是 444 的倍数。我们需要通过替换最少的字符,使得每种字符的出现频次相等(即每种字符的频次均为length/4)。求实现这一目标所需替换的最小子串长度。

输入输出说明

  • 输入:一个由 ASDF 组成的字符串。
  • 输出:实现条件的最小子串长度。

测试样例

  • 样例1:输入 "ADDF",输出 1
  • 样例2:输入 "ASAFASAFADDD",输出 3
  • 样例3:输入 "AAAASSSSDDDDFFFF",输出 0

二、解题思路

  1. 目标频次计算
    每种字符的目标频次为字符串长度 n 除以 4,即 target=n/4。

  2. 窗口滑动法

    • 使用一个滑动窗口来表示当前需要替换的子串。
    • 初始时统计所有字符的频次,判断是否已满足条件。
    • 不满足时,尝试右移窗口,缩减子串长度,直到满足条件或滑动到末尾。
  3. 条件判断

    • 在滑动窗口中,每次移动右边界,统计窗口外字符的频次是否仍满足 ≤target。
    • 如果满足条件,尝试移动左边界,进一步缩小窗口长度。
  4. 复杂度分析

    • 遍历字符串时,每个字符最多进入一次窗口,离开一次窗口,时间复杂度为 O(n)。
    • 字符频次统计操作为常数时间,整体复杂度为 O(n)。

三、代码实现

代码以及注释:

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static int solution(String input) {
        int n = input.length(); // 字符串长度
        int target = n / 4; // 每个字符的目标频次
        Map<Character, Integer> count = new HashMap<>(); // 统计字符频次
        count.put('A', 0);
        count.put('S', 0);
        count.put('D', 0);
        count.put('F', 0);

        // 初始化每种字符的频次
        for (char c : input.toCharArray()) {
            count.put(c, count.get(c) + 1);
        }

        // 如果所有字符的频次都满足目标,直接返回 0
        if (count.get('A') == target && count.get('S') == target &&
            count.get('D') == target && count.get('F') == target) {
            return 0;
        }

        int minLength = n; // 最小子串长度初始化为字符串长度
        int left = 0; // 滑动窗口的左边界

        // 滑动窗口右边界
        for (int right = 0; right < n; right++) {
            char rightChar = input.charAt(right);
            // 将右边界的字符移出窗口,更新频次
            count.put(rightChar, count.get(rightChar) - 1);

            // 判断当前窗口外的字符频次是否满足条件
            while (count.get('A') <= target && count.get('S') <= target &&
                   count.get('D') <= target && count.get('F') <= target) {
                // 更新最小子串长度
                minLength = Math.min(minLength, right - left + 1);

                // 移动左边界,尝试缩小窗口
                char leftChar = input.charAt(left);
                count.put(leftChar, count.get(leftChar) + 1); // 恢复移出窗口的字符频次
                left++; // 左边界右移
            }
        }

        return minLength; // 返回最小子串长度
    }

    public static void main(String[] args) {
        // 测试样例
        System.out.println(solution("ADDF") == 1); // 示例 1
        System.out.println(solution("ASAFASAFADDD") == 3); // 示例 2
        System.out.println(solution("SSDDFFFFAAAS") == 1); // 示例 3
        System.out.println(solution("AAAASSSSDDDDFFFF") == 0); // 示例 4
        System.out.println(solution("AAAADDDDAAAASSSS") == 4); // 示例 5
    }
}

四、知识总结

  1. 滑动窗口技巧

    • 滑动窗口是一种高效解决子串问题的算法,适合连续区间处理场景。
    • 核心是动态维护窗口的条件满足状态,尝试缩小窗口以找到最优解。
  2. 字符频次统计

    • HashMap 是统计字符频次的高效工具,插入和查找操作的时间复杂度为 O(1)。
  3. 边界条件处理

    • 初始字符频次满足目标时直接返回 0。
    • 滑动窗口中的每次条件判断需要确保所有字符均满足频次要求。

五、学习建议

  1. 滑动窗口的熟练掌握

    • 对于子串长度优化问题,滑动窗口是首选解法。需要熟悉窗口左、右边界的动态调整逻辑。
  2. 多场景测试

    • 测试时需覆盖以下情况:

      • 字符频次均已满足的输入(返回 0)。
      • 频次严重不平衡的输入(需要较大替换)。
      • 特殊字符排列(如连续一个字符)。
  3. 优化代码逻辑

    • 滑动窗口的核心是维护条件的动态性,需避免频繁计算或重复判断。

六、个人总结

本题通过滑动窗口和频次统计的结合,高效解决了替换最小子串长度的问题。在实际场景中,类似字符分布优化的问题广泛应用于文本处理、数据平衡等领域。这次刷题增强了对滑动窗口技巧的理解,同时也体会到条件约束的动态维护在算法设计中的重要性。