问题描述
小F得到了一个特殊的字符串,这个字符串只包含字符 A、S、D、F,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得 A、S、D、F 这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。
思路分析
-
问题本质:
- 需要找到一个最短的子串,通过替换这个子串中的字符,使得整个字符串中
A、S、D、F的频次相等。 - 使用滑动窗口技术来解决这个问题。滑动窗口可以动态地调整窗口的大小,找到符合条件的最短子串。
- 需要找到一个最短的子串,通过替换这个子串中的字符,使得整个字符串中
-
解决方案:
- 统计字符频次:首先统计字符串中
A、S、D、F的频次。 - 计算需要替换的数量:计算每个字符超出目标频次的数量。
- 滑动窗口:使用两个指针
left和right来维护一个滑动窗口,窗口内的字符频次必须满足A、S、D、F的替换条件。 - 动态调整窗口:不断扩大窗口(移动
right指针),直到窗口内的字符频次满足条件。然后收缩窗口(移动left指针),找到最短的满足条件的窗口。 - 返回结果:返回满足条件的最短子串长度。
- 统计字符频次:首先统计字符串中
代码详解
public class Main {
public static int solution(String input) {
int n = input.length();
int targetCount = n / 4;
// Count occurrences of each character
int countA = 0, countS = 0, countD = 0, countF = 0;
for (char c : input.toCharArray()) {
switch (c) {
case 'A': countA++; break;
case 'S': countS++; break;
case 'D': countD++; break;
case 'F': countF++; break;
}
}
// Calculate the number of replacements needed for each character
int excessA = Math.max(0, countA - targetCount);
int excessS = Math.max(0, countS - targetCount);
int excessD = Math.max(0, countD - targetCount);
int excessF = Math.max(0, countF - targetCount);
// If all characters are already balanced, return 0
if (excessA == 0 && excessS == 0 && excessD == 0 && excessF == 0) {
return 0;
}
// Sliding window to find the minimum length substring
int left = 0, right = 0;
int minLength = n;
int[] windowCount = new int[4]; // Count of A, S, D, F in the current window
while (right < n) {
// Expand the window
char rightChar = input.charAt(right);
switch (rightChar) {
case 'A': windowCount[0]++; break;
case 'S': windowCount[1]++; break;
case 'D': windowCount[2]++; break;
case 'F': windowCount[3]++; break;
}
right++;
// Check if the current window satisfies the condition
while (windowCount[0] >= excessA && windowCount[1] >= excessS && windowCount[2] >= excessD && windowCount[3] >= excessF) {
minLength = Math.min(minLength, right - left);
// Contract the window
char leftChar = input.charAt(left);
switch (leftChar) {
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) {
// You can add more test cases here
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
}
}
知识总结
-
滑动窗口技术:
- 滑动窗口是一种动态调整窗口大小的算法,适用于寻找满足特定条件的最小子串或子数组。
- 通过两个指针
left和right来维护窗口的边界,逐步扩大和收缩窗口,找到最优解。
-
字符频次统计:
- 使用
switch语句统计字符串中每个字符的频次,简单明了。 - 计算每个字符超出目标频次的数量,确定需要替换的数量。
- 使用
-
条件判断:
- 在滑动窗口过程中,需要不断检查窗口内的字符频次是否满足条件。
- 使用
while循环来不断收缩窗口,找到最短的满足条件的子串。
-
代码优化:
- 通过使用数组
windowCount来记录窗口内每个字符的频次,可以提高代码的执行效率。 - 在
main函数中添加更多的测试用例,确保代码的正确性和鲁棒性。
- 通过使用数组
个人理解与学习建议
-
理解滑动窗口:
- 滑动窗口是一种非常实用的算法技术,通过动态调整窗口的大小,可以高效地解决很多字符串和数组的问题。
- 通过这个题目,可以加深对滑动窗口的理解,学会如何在实际问题中应用。
-
代码可读性:
- 使用有意义的变量名,如
targetCount、excessA等,可以提高代码的可读性。 - 添加注释,解释每一步的逻辑,有助于他人理解代码。
- 使用有意义的变量名,如
-
性能考虑:
- 滑动窗口的时间复杂度为 O(n)O(n),适用于中等规模的输入。
- 注意优化字符频次统计和条件判断的过程,避免不必要的计算。
-
学习建议:
- 多做练习:通过多做类似的题目,可以提高对滑动窗口的理解和应用能力。
- 思考边界情况:在编写代码时,思考各种边界情况,确保代码的鲁棒性。
- 代码模块化:将代码分成多个函数或模块,可以提高代码的可维护性和可读性。