一. 问题描述
小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
二. 思路解析
2.1 问题理解
题目要求我们通过尽可能少的替换操作,使得字符串中字符 A、S、D、F 的出现频次相等。字符串的长度总是4的倍数,因此每个字符的目标频次应该是 length / 4。
2.2 数据结构选择
- 频率数组:我们使用一个长度为4的数组
freq来记录字符A、S、D、F的出现次数。 - 滑动窗口:为了找到最小的子串长度,我们使用滑动窗口技术。滑动窗口可以帮助我们在 O(n) 的时间复杂度内找到满足条件的最小子串。
三. 解题步骤
3.1 算法步骤
-
计算频率:
- 遍历输入字符串,统计每个字符的出现次数,并存储在
freq数组中。
- 遍历输入字符串,统计每个字符的出现次数,并存储在
-
计算目标频率:
- 计算每个字符的目标频率
targetFreq = length / 4。
- 计算每个字符的目标频率
-
计算需要替换的字符数量:
- 对于每个字符,计算其需要替换的数量
excess[i] = max(0, freq[i] - targetFreq)。如果某个字符的频率已经达到目标频率,则excess[i]为0。
- 对于每个字符,计算其需要替换的数量
-
滑动窗口:
- 使用两个指针
left和right来表示滑动窗口的左右边界。 - 初始化
left和right为0,并初始化一个数组windowCount来记录当前窗口内每个字符的出现次数。 - 扩展窗口:每次将
right指针向右移动,并更新windowCount。 - 检查窗口是否包含所有需要替换的字符:如果
windowCount中每个字符的出现次数都大于等于excess中对应的值,说明当前窗口满足条件。 - 收缩窗口:如果当前窗口满足条件,尝试将
left指针向右移动,以找到更小的窗口。 - 更新最小窗口长度
minLength。
- 使用两个指针
-
返回结果:
- 最终返回
minLength,即满足条件的最小子串长度。
- 最终返回
3.2 复杂度分析
3.2.1 时间复杂度分析
-
频率计算:
- 遍历整个输入字符串一次,计算每个字符的频率。这一步的时间复杂度是 O(n),其中 n 是输入字符串的长度。
-
滑动窗口:
- 滑动窗口的
right指针会遍历整个字符串一次,时间复杂度是 O(n)。 - 在每次扩展窗口时,可能会进入一个内部循环来收缩窗口。但由于每个字符最多只会被
left和right指针各访问一次,因此收缩窗口的总时间复杂度也是 O(n)。
- 滑动窗口的
综合以上两部分,整个算法的时间复杂度是 O(n)。
3.2.2空间复杂度
-
频率数组:
- 使用了一个长度为4的数组
freq来存储字符频率,空间复杂度是 O(1)。
- 使用了一个长度为4的数组
-
滑动窗口计数数组:
- 使用了一个长度为4的数组
windowCount来存储当前窗口内字符的计数,空间复杂度是 O(1)。
- 使用了一个长度为4的数组
-
其他变量:
- 使用了几个整数变量(如
targetFreq、left、right、minLength等),这些变量的空间复杂度都是 O(1)。
- 使用了几个整数变量(如
综合以上各部分,整个算法的空间复杂度是 O(1)。
四. Code
public class Main {
public static int solution(String input) {
// 计算每个字符的频率
int[] freq = new int[4]; // 0: A, 1: S, 2: D, 3: F
for (char c : input.toCharArray()) {
switch (c) {
case 'A': freq[0]++; break;
case 'S': freq[1]++; break;
case 'D': freq[2]++; break;
case 'F': freq[3]++; break;
}
}
// 计算目标频率
int targetFreq = input.length() / 4;
// 计算需要替换的字符数量
int[] excess = new int[4];
for (int i = 0; i < 4; i++) {
excess[i] = Math.max(0, freq[i] - targetFreq);
}
// 如果所有字符的频率已经平衡,直接返回0
if (excess[0] == 0 && excess[1] == 0 && excess[2] == 0 && excess[3] == 0) {
return 0;
}
// 使用滑动窗口找到最小的子串长度
int left = 0, right = 0;
int minLength = input.length();
int[] windowCount = new int[4];
while (right < input.length()) {
// 扩展窗口
switch (input.charAt(right)) {
case 'A': windowCount[0]++; break;
case 'S': windowCount[1]++; break;
case 'D': windowCount[2]++; break;
case 'F': windowCount[3]++; break;
}
right++;
// 检查窗口是否包含所有需要替换的字符
while (windowCount[0] >= excess[0] && windowCount[1] >= excess[1] &&
windowCount[2] >= excess[2] && windowCount[3] >= excess[3]) {
minLength = Math.min(minLength, right - left);
// 收缩窗口
switch (input.charAt(left)) {
case 'A': windowCount[0]--; break;
case 'S': windowCount[1]--; break;
case 'D': windowCount[2]--; break;
case 'F': windowCount[3]--; break;
}
left++;
}
}
return minLength;
}
public static void main(String[] args) {
// 你可以添加更多测试用例
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
}
}
五. 总结
使用AI解题可以帮助开发者更高效、准确地解决问题,同时提供丰富的学习资源和指导,帮助开发者不断提升编程技能。通过与AI的互动,开发者可以更好地理解问题、优化代码,并逐步提升自己的编程能力。