使用双指针解决字符平衡问题:最小替换子串长度
在算法领域,字符串操作是非常常见的一类问题。今天,我们以一个特殊的字符串操作问题为例,探讨如何高效解决通过最少替换使字符串字符频次均衡的问题。
问题描述
小F得到了一串特殊的字符串,这个字符串仅由字符 A、S、D 和 F 组成,并且字符串的长度始终为4的倍数。小F的任务是通过尽可能少的字符替换操作,使得这四个字符在字符串中的出现频次完全相等。
举例来说:
- 输入
"ADDF",通过一次替换即可达到平衡(如将一个D替换为A)。 - 输入
"AAAASSSSDDDDFFFF",由于字符本身已经平衡,无需替换。 - 输入
"ASAFASAFADDD",最小需要替换 3 个字符。
解题思路
要解决这个问题,可以采用 滑动窗口法,这是一种经典的字符串处理技巧,通过两个指针来定义一个动态窗口范围,逐步调整窗口大小,从而在复杂度较低的情况下找到最优解。
具体步骤如下:
- 确定目标频率:
由于字符串长度是4的倍数,每个字符的目标频次为字符串长度 / 4。 - 计算超出频次的字符:
对输入字符串进行遍历,统计A、S、D、F的频次。对于超过目标频次的字符,记录超出的数量,这些字符需要通过替换操作来达到平衡。 - 滑动窗口遍历:
使用双指针构建一个动态窗口,遍历字符串。当窗口中包含所有超出频次的字符时,尝试缩小窗口大小,记录此时的最小长度。 - 有效窗口判断:
通过检查窗口内是否满足替换需求来验证窗口有效性。如果窗口内的字符已足够抵消超出频次,则窗口是有效的。
代码实现
以下是完整的 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);
}
}
代码分析
-
复杂度分析:
- 时间复杂度:
O(n),双指针遍历字符串,外加常数级的字符统计操作。 - 空间复杂度:
O(1),使用固定大小的哈希表存储字符频次。
- 时间复杂度:
-
核心逻辑:
- 滑动窗口的右指针扩展窗口,左指针缩小窗口。
- 在每次窗口有效时,记录当前窗口大小,并尝试找到更小的窗口。