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

52 阅读3分钟

使用双指针解决字符平衡问题:最小替换子串长度

在算法领域,字符串操作是非常常见的一类问题。今天,我们以一个特殊的字符串操作问题为例,探讨如何高效解决通过最少替换使字符串字符频次均衡的问题。


问题描述

小F得到了一串特殊的字符串,这个字符串仅由字符 ASD 和 F 组成,并且字符串的长度始终为4的倍数。小F的任务是通过尽可能少的字符替换操作,使得这四个字符在字符串中的出现频次完全相等。

举例来说:

  • 输入 "ADDF",通过一次替换即可达到平衡(如将一个 D 替换为 A)。
  • 输入 "AAAASSSSDDDDFFFF",由于字符本身已经平衡,无需替换。
  • 输入 "ASAFASAFADDD",最小需要替换 3 个字符。

解题思路

要解决这个问题,可以采用 滑动窗口法,这是一种经典的字符串处理技巧,通过两个指针来定义一个动态窗口范围,逐步调整窗口大小,从而在复杂度较低的情况下找到最优解。

具体步骤如下:

  1. 确定目标频率
    由于字符串长度是4的倍数,每个字符的目标频次为 字符串长度 / 4
  2. 计算超出频次的字符
    对输入字符串进行遍历,统计 ASDF 的频次。对于超过目标频次的字符,记录超出的数量,这些字符需要通过替换操作来达到平衡。
  3. 滑动窗口遍历
    使用双指针构建一个动态窗口,遍历字符串。当窗口中包含所有超出频次的字符时,尝试缩小窗口大小,记录此时的最小长度。
  4. 有效窗口判断
    通过检查窗口内是否满足替换需求来验证窗口有效性。如果窗口内的字符已足够抵消超出频次,则窗口是有效的。

代码实现

以下是完整的 Java 代码实现:

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

public class Main {
    public static int solution(String input) {
        int n = input.length();
        int targetFreq = n / 4;

        // 统计每个字母的词频
        Map<Character, Integer> count = new HashMap<>();
        count.put('A', 0);
        count.put('D', 0);
        count.put('F', 0);
        count.put('S', 0);

        for (char c : input.toCharArray()) {
            count.put(c, count.get(c) + 1);
        }

        // 计算超出正确词频的字母和频率
        Map<Character, Integer> excess = new HashMap<>();
        for (Map.Entry<Character, Integer> entry : count.entrySet()) {
            char c = entry.getKey();
            int freq = entry.getValue();
            if (freq > targetFreq) {
                excess.put(c, freq - targetFreq);
            }
        }
        if (excess.isEmpty() ) {
            return 0;
        }

        int left = 0, minLength = n;

        // 遍历字符串,移动左右指针
        for (int right = 0; right < n; right++) {
            char currentChar = input.charAt(right);

            // 如果当前字符是超出频次的字母,减少其超出值
            if (excess.containsKey(currentChar)) {
                excess.put(currentChar, excess.get(currentChar) - 1);
            }

            // 当窗口是有效替换串时,尝试缩小左边界
            while (isValidWindow(excess)) {
                minLength = Math.min(minLength, right - left + 1);

                char leftChar = input.charAt(left);
                if (excess.containsKey(leftChar)) {
                    excess.put(leftChar, excess.get(leftChar) + 1);
                }
                left++;
            }
        }

        return minLength;
    }

    // 检查当前窗口是否为有效替换串
    private static boolean isValidWindow(Map<Character, Integer> excess) {
        for (int freq : excess.values()) {
            if (freq > 0) {
                return false;
            }
        }
        return true;
    }

    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);
        System.out.println(solution("AAAASSSSDDDDFFFF") == 0);
    }
}

代码分析

  1. 复杂度分析

    • 时间复杂度O(n),双指针遍历字符串,外加常数级的字符统计操作。
    • 空间复杂度O(1),使用固定大小的哈希表存储字符频次。
  2. 核心逻辑

    • 滑动窗口的右指针扩展窗口,左指针缩小窗口。
    • 在每次窗口有效时,记录当前窗口大小,并尝试找到更小的窗口。