问题描述
小F得到了一个特殊的字符串,这个字符串只包含字符A、S、D、F,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得A、S、D、F这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。
测试样例
样例1: 输入:input = "ADDF" 输出:1
样例2: 输入:input = "ASAFASAFADDD" 输出:3
样例3: 输入:input = "SSDDFFFFAAAS" 输出:1
样例4: 输入:input = "AAAASSSSDDDDFFFF" 输出:0
样例5: 输入:input = "AAAADDDDAAAASSSS" 输出:4
问题分析
给定一个只包含字符 A、S、D 和 F 的字符串,我们的目标是通过最少的替换操作,使得这四个字符的出现频次在字符串中相等。根据题目描述,字符串的长度总是 4 的倍数,因此目标是每个字符的出现频次为 n / 4(其中 n 是字符串的长度)。
例如:
- 如果字符串的长度为 12,目标是每个字符出现的次数为 12 / 4 = 3。
- 如果字符串的长度为 16,目标是每个字符出现的次数为 16 / 4 = 4。
问题要求我们找到一个最小的子串,替换该子串中的字符,使得最终所有字符的频次相等。
解题思路
1.统计字符频率: 首先统计字符串中每个字符 A、S、D、F 的出现频次。目标是让每个字符的频次接近字符串总长度的四分之一,即 n / 4(其中 n 是字符串的长度)。
2.计算需要替换的字符数: 对于每个字符,我们计算它超出目标频次的部分,这部分字符需要被替换成其他字符。例如,若字符 A 的频次超过 n / 4,则多余的 A 需要被替换成其他字符。
3.滑动窗口: 使用滑动窗口技术来找到最小的子串长度,满足以下条件:该子串中包含足够数量的需要替换的字符。滑动窗口通过两个指针 left 和 right 来扩展和收缩窗口。窗口中的字符被检查是否可以通过替换来平衡字符频次。
4.判断是否满足条件: 在滑动窗口中,我们不断检查窗口内的字符是否能够替换成目标频次。如果满足条件,更新最小子串长度,并尝试通过移动 left 指针来缩小窗口,直到不满足条件。
5.输出结果: 如果没有需要替换的字符,直接返回 0。否则返回最小的子串长度。
详细步骤
1.初始化变量:
- count[ ] 用来统计字符串中 A、S、D、F 的频次。
- targetCount 为每个字符的目标频次,即 n / 4。
- needReplace[ ] 存储每个字符需要替换的次数,即 max(0, count[i] - targetCount)。
2.滑动窗口:
- 使用 left 和 right 两个指针初始化滑动窗口,windowCount[ ] 用来统计当前窗口中各个字符的频次。
- 扩展窗口:移动 right 指针,每次扩展一个字符。
- 收缩窗口:当窗口中的字符已经可以满足替换要求时,尝试移动 left 指针,缩小窗口并更新最小子串长度。
3.终止条件:
- 若窗口中的字符已经足够替换(即窗口内有足够多的字符需要替换),更新最小子串长度,并尝试收缩窗口。
4.返回结果:
- 最终返回最小的子串长度,或者如果所有字符的频次已经平衡,返回 0。
代码实现
public static int solution(String input) {
int n = input.length();
int targetCount = n / 4;
// 统计每个字符的初始频次
int[] count = new int[4]; // 0: A, 1: S, 2: D, 3: F
for (char c : input.toCharArray()) {
if (c == 'A') count[0]++;
else if (c == 'S') count[1]++;
else if (c == 'D') count[2]++;
else if (c == 'F') count[3]++;
}
// 计算每个字符需要替换的次数
int[] needReplace = new int[4];
for (int i = 0; i < 4; i++) {
needReplace[i] = Math.max(0, count[i] - targetCount);
}
// 如果所有字符的频次已经相等,直接返回0
if (needReplace[0] == 0 && needReplace[1] == 0 && needReplace[2] == 0 && needReplace[3] == 0) {
return 0;
}
// 使用滑动窗口找到最小的替换子串长度
int left = 0, right = 0;
int minLen = n;
int[] windowCount = new int[4];
while (right < n) {
char rightChar = input.charAt(right);
if (rightChar == 'A') windowCount[0]++;
else if (rightChar == 'S') windowCount[1]++;
else if (rightChar == 'D') windowCount[2]++;
else if (rightChar == 'F') windowCount[3]++;
while (left <= right && windowCount[0] >= needReplace[0] && windowCount[1] >= needReplace[1] && windowCount[2] >= needReplace[2] && windowCount[3] >= needReplace[3]) {
minLen = Math.min(minLen, right - left + 1);
char leftChar = input.charAt(left);
if (leftChar == 'A') windowCount[0]--;
else if (leftChar == 'S') windowCount[1]--;
else if (leftChar == 'D') windowCount[2]--;
else if (leftChar == 'F') windowCount[3]--;
left++;
}
right++;
}
return minLen;
}
public static void main(String[] args) {
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
System.out.println(solution("SSDDFFFFAAAS") == 1);
System.out.println(solution("AAAASSSSDDDDFFFF") == 0);
System.out.println(solution("AAAADDDDAAAASSSS") == 4);
}
}
解析:
- count[ ] 数组: 用来存储每个字符出现的频次。
- needReplace[ ] 数组: 存储每个字符需要替换的次数,若字符数量超过目标频次,则记录需要替换的数量。
- 滑动窗口: left 和 right 指针用于动态维护一个窗口,当窗口内的字符数量足够时,更新最小长度并收缩窗口。
总结
这道题的核心在于通过 统计频次 和 滑动窗口 技术,找到最小的子串并进行替换,以满足所有字符频次相等。通过合理使用滑动窗口和频次比较,我们能够有效地解决这个问题,找到最小的替换子串长度。