问题描述
小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
思路与步骤
-
计算目标频次:因为字符串长度是 4 的倍数,所以每个字符的目标频次是
targetCount = n / 4。 -
初始化频次计数:遍历整个字符串,计算每个字符的实际出现频次。如果所有字符的频次都小于或等于
targetCount,说明无需替换字符,直接返回 0。 -
使用滑动窗口找到最小子串:
- 通过滑动窗口(使用双指针
left和right)找到包含多余字符的最小子串。 - 将窗口右边界(
right)向右扩展,并减少当前字符的计数。 - 检查窗口内的字符频次是否满足条件,即每种字符的频次都小于或等于
targetCount。 - 当满足条件时,记录窗口长度并尝试缩小窗口左边界(
left),直到不再满足条件。
- 通过滑动窗口(使用双指针
-
返回结果:输出所有满足条件的窗口中最小的长度。
举例
假设输入字符串为 "ASAFASAFADDD",目标是让每个字符 A、S、D、F 出现的频次为 3。
-
初始化:计算各字符初始频次,得到
A: 4,S: 3,D: 3,F: 2。 -
滑动窗口操作:
- 从
left = 0,right = 0开始,扩大右边界,计算字符频次。 - 一旦窗口内的字符频次满足
targetCount,即A: 3,S: 3,D: 3,F: 3,记录窗口长度并缩小左边界。
- 从
-
窗口操作:
right = 0 -> 3:"ASAF"频次不满足。right = 4 -> 5:窗口达到3后满足条件,记录长度。
import java.util.*;;
public class Main {
public static int solution(String input) {
int n = input.length();
int targetCount = n / 4; // 每个字符的目标频次
// 计算初始频次
Map<Character, Integer> countMap = new HashMap<>();
for (char ch : input.toCharArray()) {
countMap.put(ch, countMap.getOrDefault(ch, 0) + 1);
}
// 检查是否已经满足条件
if (countMap.getOrDefault('A', 0) <= targetCount &&
countMap.getOrDefault('S', 0) <= targetCount &&
countMap.getOrDefault('D', 0) <= targetCount &&
countMap.getOrDefault('F', 0) <= targetCount) {
return 0;
}
int minLength = n;
int left = 0;
// 滑动窗口
for (int right = 0; right < n; right++) {
countMap.put(input.charAt(right), countMap.get(input.charAt(right)) - 1);
// 窗口内字符满足条件时,尝试缩小窗口
while (countMap.getOrDefault('A', 0) <= targetCount &&
countMap.getOrDefault('S', 0) <= targetCount &&
countMap.getOrDefault('D', 0) <= targetCount &&
countMap.getOrDefault('F', 0) <= targetCount) {
minLength = Math.min(minLength, right - left + 1);
countMap.put(input.charAt(left), countMap.get(input.charAt(left)) + 1);
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);
}
}
代码详解
-
目标频次初始化:
targetCount表示每个字符的理想频次,为n / 4。 -
初始频次计算:
countMap用于记录初始字符串中A、S、D、F的出现频次。 -
滑动窗口:
- 遍历字符串,逐步扩大窗口的右边界。
- 每次扩大右边界时,减少该字符的频次,以便保持当前窗口频次更新。
- 当窗口内的字符频次满足
targetCount时,更新最小窗口长度minLength,并尝试缩小左边界。
-
时间复杂度:由于滑动窗口只需遍历字符串一次,因此时间复杂度为
O(n)。