问题描述
小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
使用 MarsCode AI 来解题
我:需要一点思路提示
MarsCode AI:
好的,让我们来分析一下这个问题。
问题理解
我们需要找到一个最小的子串长度,通过替换这个子串中的字符,使得整个字符串中字符 A
、S
、D
、F
的出现频次相等。由于字符串的长度总是4的倍数,因此每个字符的理想频次应该是 len(input) / 4
。
数据结构选择
- 计数器:我们可以使用一个计数器来统计每个字符
A
、S
、D
、F
的出现次数。 - 滑动窗口:为了找到最小的替换子串,我们可以使用滑动窗口技术来遍历字符串,并计算每个窗口内需要替换的字符数。
算法步骤
- 初始化计数器:统计字符串中每个字符的出现次数。
- 计算理想频次:计算每个字符的理想频次
ideal_count = len(input) / 4
。 - 计算需要替换的字符数:对于每个字符,计算需要替换的字符数
needed_replacements = max(0, count - ideal_count)
。 - 滑动窗口:使用滑动窗口技术,从左到右遍历字符串,计算每个窗口内需要替换的字符数,并记录最小的窗口长度。
伪代码
public class Main {
public static int solution(String input) {
// 初始化计数器
int[] count = new int[4];
for (char c : input.toCharArray()) {
count[charToIndex(c)]++;
}
// 计算理想频次
int ideal_count = input.length() / 4;
// 计算需要替换的字符数
int needed_replacements = 0;
for (int i = 0; i < 4; i++) {
if (count[i] > ideal_count) {
needed_replacements += count[i] - ideal_count;
}
}
// 滑动窗口
int left = 0, right = 0;
int[] window_count = new int[4];
int min_length = input.length();
while (right < input.length()) {
// 扩展窗口
window_count[charToIndex(input.charAt(right))]++;
right++;
// 收缩窗口
while (canReplace(window_count, count, ideal_count)) {
min_length = Math.min(min_length, right - left);
window_count[charToIndex(input.charAt(left))]--;
left++;
}
}
return min_length;
}
// 辅助函数:将字符转换为索引
private static int charToIndex(char c) {
switch (c) {
case 'A': return 0;
case 'S': return 1;
case 'D': return 2;
case 'F': return 3;
default: throw new IllegalArgumentException("Invalid character");
}
}
// 辅助函数:判断当前窗口是否可以替换
private static boolean canReplace(int[] window_count, int[] count, int ideal_count) {
for (int i = 0; i < 4; i++) {
if (window_count[i] < count[i] - ideal_count) {
return false;
}
}
return true;
}
public static void main(String[] args) {
// 你可以添加更多测试用例
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
}
}
我:没理解这个输出要的是什么
MarsCode AI:
好的,让我们详细解释一下这个问题的输出要求。
问题描述回顾
小F的任务是通过尽可能少的替换,使得字符串中字符 A
、S
、D
、F
的出现频次相等。我们需要找到一个最小的子串长度,通过替换这个子串中的字符,使得整个字符串中这四个字符的频次相等。
输出解释
输出是一个整数,表示实现这一条件的最小子串长度。具体来说:
- 理想频次:由于字符串的长度总是4的倍数,每个字符的理想频次应该是
len(input) / 4
。 - 需要替换的字符数:对于每个字符,计算需要替换的字符数
needed_replacements = max(0, count - ideal_count)
。 - 最小子串长度:我们需要找到一个最小的子串,通过替换这个子串中的字符,使得整个字符串中字符
A
、S
、D
、F
的频次相等。
示例解释
让我们通过几个示例来进一步理解:
样例1
输入:"ADDF"
输出:1
理想频次:4 / 4 = 1
当前频次:A: 1, S: 0, D: 2, F: 1
需要替换的字符数:A: 0, S: 1, D: 1, F: 0
最小子串长度:只需要替换一个字符(例如将一个 D
替换为 S
),所以输出为 1
。
样例2
输入:"ASAFASAFADDD"
输出:3
理想频次:12 / 4 = 3
当前频次:A: 5, S: 3, D: 3, F: 1
需要替换的字符数:A: 2, S: 0, D: 0, F: 2
最小子串长度:需要替换三个字符(例如将两个 A
替换为 F
,一个 F
替换为 A
),所以输出为 3
。
看完以后,我的理解是这样的,先统计所有的字符个数,看看哪个字符多了几个,比如A多了2个,那么就会有其他字符的和少了2个,现在找出字符数量更多的,假如A多了2个,D多了1一个,那么通过滑动窗口找最小窗口,窗口内的字符串必须至少包含2个A和一个D,同时保证窗口最小。
但是运行代码报错如下:
[WARN]
[WARN] 解答错误,请修正代码后重新提交
[WARN] 第 4 个测试用例运行错误,3/35 个用例测试通过
[WARN]
[WARN] 测试用例输入:
[WARN] input = "AAAASSSSDDDDFFFF"
[ERROR]
[ERROR] java.lang.StringIndexOutOfBoundsException: String index out of range: 16
at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
at java.base/java.lang.String.charAt(String.java:1519)
at Main.solution(Main.java:34)
at TestRunnerMain.lambda$runCase$0(TestRunnerMain.java:56)
at java.base/java.lang.Thread.run(Thread.java:842)
把错误给MarsCode AI,并说说有没有哪些情况没有考虑到
MarsCode AI:
可能遗漏的情况
- 边界情况:字符串长度为4的倍数,但所有字符的频次已经相等。
- 滑动窗口的初始化:确保滑动窗口的初始状态正确。
- 多余字符的计算:确保多余字符的计算正确。
检查边界情况
- 所有字符频次相等:如果所有字符的频次已经相等,那么不需要进行任何替换,直接返回
0
。
修改代码
public class Main {
public static int solution(String input) {
// 初始化计数器
int[] count = new int[4];
for (char c : input.toCharArray()) {
count[charToIndex(c)]++;
}
// 计算理想频次
int ideal_count = input.length() / 4;
// 检查是否所有字符频次已经相等
boolean allEqual = true;
for (int i = 0; i < 4; i++) {
if (count[i] != ideal_count) {
allEqual = false;
break;
}
}
if (allEqual) {
return 0;
}
// 计算多余字符数
int[] excess = new int[4];
for (int i = 0; i < 4; i++) {
excess[i] = Math.max(0, count[i] - ideal_count);
}
// 滑动窗口
int left = 0, right = 0;
int[] window_count = new int[4];
int min_length = input.length();
while (right < input.length()) {
// 扩展窗口
window_count[charToIndex(input.charAt(right))]++;
right++;
// 收缩窗口
while (containsExcess(window_count, excess)) {
min_length = Math.min(min_length, right - left);
window_count[charToIndex(input.charAt(left))]--;
left++;
}
}
return min_length;
}
// 辅助函数:将字符转换为索引
private static int charToIndex(char c) {
switch (c) {
case 'A': return 0;
case 'S': return 1;
case 'D': return 2;
case 'F': return 3;
default: throw new IllegalArgumentException("Invalid character");
}
}
// 辅助函数:判断当前窗口是否包含所有多余字符
private static boolean containsExcess(int[] window_count, int[] excess) {
for (int i = 0; i < 4; i++) {
if (window_count[i] < excess[i]) {
return false;
}
}
return true;
}
public static void main(String[] args) {
// 你可以添加更多测试用例
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
}
}
通过