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

50 阅读5分钟

题目解析与解题思路

在计算机科学中,字符串处理是一个常见的问题领域,特别是在算法竞赛和实际开发中。本文将深入探讨一个特定的字符串处理问题——如何通过最少的字符替换操作,使得字符串中仅包含字符A、S、D和F,并且这些字符出现的频次相等。我们将基于Java语言来实现这一解决方案,并详细解释每一步的设计思路。

image.png

问题描述

给定一个只包含字符A、S、D和F的字符串 s,我们的目标是找到一个最小子串,通过替换该子串中的字符,可以使整个字符串中每个字符(A、S、D、F)的出现次数相同。如果已经满足条件,则返回0。我们需要计算出这个最小子串的长度。

解决方案概述

  1. 频率统计:首先,我们统计字符串中每个字符的出现次数。
  2. 确定基准频率:接着,根据字符串长度计算出理想的字符频率,即每个字符应该出现的次数。
  3. 构建目标模式:基于当前字符频率与理想频率之间的差异,构造一个“目标模式”字符串,它包含了所有需要被替换掉的多余字符。
  4. 寻找最小窗口:利用滑动窗口技术,找出能够覆盖“目标模式”的最小子串。
  5. 结果输出:最终返回找到的最小子串的长度作为答案。

代码实现

下面,我们将详细介绍上述步骤的具体实现方式。

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

public class CharacterBalance {

    public static int solution(String s) {
        Map<Character, Integer> charCount = new HashMap<>();
        int minReplacement = s.length() / 4; // 计算每个字符的理想频率
        for (char c : s.toCharArray()) {
            charCount.put(c, charCount.getOrDefault(c, 0) + 1);
        }

        // 构造目标模式字符串
        StringBuilder targetPattern = new StringBuilder();
        for (Map.Entry<Character, Integer> entry : charCount.entrySet()) {
            if (entry.getValue() > minReplacement) {
                int extra = entry.getValue() - minReplacement;
                for (int i = 0; i < extra; i++) {
                    targetPattern.append(entry.getKey());
                }
            }
        }

        String t = targetPattern.toString();

        // 如果不需要任何替换,直接返回0
        if (t.isEmpty()) return 0;

        // 使用滑动窗口技术寻找最小覆盖窗口
        return minWindow(s, t).length();
    }

    private static String minWindow(String s, String t) {
        if (s == null || t == null || s.length() < t.length())
            return "";

        Map<Character, Integer> need = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        int left = 0, right = 0, valid = 0, start = 0, len = Integer.MAX_VALUE;
        Map<Character, Integer> window = new HashMap<>();

        while (right < s.length()) {
            char c = s.charAt(right++);
            if (need.containsKey(c)) {
                window.put(c, window.getOrDefault(c, 0) + 1);
                if (window.get(c).equals(need.get(c))) {
                    valid++;
                }
            }

            // 当窗口满足条件时,尝试缩小窗口
            while (valid == need.size()) {
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                char d = s.charAt(left++);
                if (need.containsKey(d)) {
                    if (window.get(d).equals(need.get(d))) {
                        valid--;
                    }
                    window.put(d, window.get(d) - 1);
                }
            }
        }

        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

详解关键点

  • 频率统计:使用HashMap存储每个字符及其出现次数,这一步骤对于后续分析至关重要。
  • 理想频率计算:由于题目要求每个字符的出现次数相等,因此我们可以通过字符串长度除以4来得到每个字符的理想出现次数。
  • 目标模式构建:根据当前字符频率与理想频率之差,构建一个代表需要替换字符的目标模式。这一步骤帮助我们明确哪些部分是需要特别关注的。
  • 滑动窗口技术:这是一种高效的方法,用于在长字符串中寻找满足特定条件的最短连续子串。通过维护两个指针(left, right)和一个窗口状态(window),我们可以动态调整窗口大小,确保其始终覆盖目标模式,同时保持尽可能小的尺寸。
  • 边界情况处理:当输入字符串本身已经满足条件时,直接返回0,避免不必要的计算。

性能考量

  • 时间复杂度:主要由频率统计和滑动窗口搜索两部分组成。频率统计的时间复杂度为O(n),其中n是字符串长度;滑动窗口搜索的时间复杂度同样为O(n),因为每个字符最多被访问两次。因此整体时间复杂度为O(n)。
  • 空间复杂度:使用了额外的空间来存储字符频率和窗口状态,空间复杂度为O(1),因为字符集固定为{A, S, D, F}。

深入探讨与优化

代码优化与改进

在上述解决方案中,我们已经实现了一个基本的算法来解决题目要求。然而,为了使程序更加健壮和高效,我们可以考虑一些额外的优化措施。

  1. 输入验证:在实际应用中,我们应该对输入进行验证,确保输入字符串只包含A、S、D、F这四个字符。如果输入不合法,则可以抛出异常或返回错误信息。
  2. 数据结构选择:虽然使用HashMap来存储字符频率是合理的,但对于只有四个可能字符的情况,可以考虑使用固定大小的数组来替代HashMap,这样可以减少哈希冲突的可能性,并且访问速度更快。
  3. 边界条件处理:除了直接满足条件的情况(即不需要任何替换),还应考虑极端情况,如字符串长度不是4的倍数时如何处理。在这种情况下,可能需要额外的逻辑来决定是否可以通过替换达到平衡状态。

结论

本题提供了一个典型的字符串处理场景,通过结合频率统计、数学运算以及滑动窗口技术,我们能够有效地解决这个问题。此方法不仅适用于竞赛环境,也可以扩展到更广泛的应用场景中,如文本分析、数据清洗等领域。理解并掌握这种类型的算法设计思想,对于提升编程能力和解决实际问题都有极大的帮助。