题目解析:解决“最小替换子串长度”问题
思路分析:
问题理解
我们需要通过尽可能少的替换操作,使得字符串中字符A、S、D、F的出现频次相等。字符串的长度总是4的倍数,因此每个字符的目标出现次数是字符串长度除以4。
数据结构选择
- 计数数组:用于统计每个字符的出现次数。
- 滑动窗口:用于找到最小的子串,使得该子串中包含足够多的需要替换的字符。
解题步骤:
-
统计字符出现次数:遍历字符串,统计每个字符的出现次数。
-
计算需要替换的次数:对于每个字符,计算其超出目标次数的部分。
-
滑动窗口:
- 初始化左右指针
left和right,以及一个用于记录当前窗口内字符出现次数的数组windowCount。 - 扩展窗口:右指针向右移动,更新
windowCount。 - 检查当前窗口是否满足替换条件:如果当前窗口内的字符出现次数满足替换条件,记录当前窗口长度,并尝试收缩窗口(左指针向右移动)。
- 重复上述步骤,直到右指针遍历完整个字符串。
- 初始化左右指针
-
返回结果:返回找到的最小子串长度。
图解:
-
滑动窗口:
- 统计字符出现次数:首先统计字符串中每个字符的出现次数。
- 计算需要替换的次数:计算每个字符超出目标次数的部分。
- 滑动窗口:使用滑动窗口技术来找到最小的子串长度,使得该子串中包含足够多的需要替换的字符。
-
前缀和 + 哈希表:
- 前缀和:计算每个位置的前缀和,记录每个字符的出现次数。
- 哈希表:使用哈希表记录每个前缀和的状态,找到最小的子串长度。
代码详解(使用滑动窗口):
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[] excess = new int[4];
for (int i = 0; i < 4; i++) {
excess[i] = Math.max(0, count[i] - targetCount);
}
// 如果所有字符都已经满足条件,直接返回0
if (excess[0] == 0 && excess[1] == 0 && excess[2] == 0 && excess[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]++;
right++;
// 检查当前窗口是否满足替换条件
while (left < right && windowCount[0] >= excess[0] && windowCount[1] >= excess[1] && windowCount[2] >= excess[2] && windowCount[3] >= excess[3]) {
minLen = Math.min(minLen, right - left);
// 收缩窗口
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++;
}
}
return minLen;
}
数据结构
- 数组:用于统计字符的出现次数和记录当前窗口内字符的出现次数。
- 哈希表:用于记录每个前缀和状态的最早出现位置。
- 滑动窗口:用于在O(n)的时间复杂度内找到最小的子串长度。
- 前缀和:用于记录每个位置的前缀和状态。
时间复杂度分析
- 滑动窗口方法:时间复杂度为O(n)。
- 前缀和 + 哈希表方法:时间复杂度为O(n)。
问题分解与抽象
-
统计字符出现次数:
- 遍历字符串,统计每个字符的出现次数。
- 使用一个数组来存储每个字符的出现次数。
- 输入:字符串
input。 - 输出:一个数组
count,表示每个字符的出现次数。
-
计算需要替换的次数:
- 计算每个字符超出目标次数的部分。
- 使用一个数组来存储每个字符需要替换的次数。
- 输入:数组
count,目标出现次数targetCount。 - 输出:一个数组
excess,表示每个字符需要替换的次数。
-
滑动窗口:
- 使用滑动窗口技术来找到最小的子串长度,使得该子串中包含足够多的需要替换的字符。
- 维护一个窗口,扩展右指针并检查当前窗口是否满足替换条件,如果满足则收缩左指针。
- 输入:字符串
input,数组excess。 - 输出:最小的子串长度
minLen。
-
前缀和 + 哈希表:
- 计算每个位置的前缀和状态。
- 使用哈希表记录每个前缀和状态的最早出现位置。
- 通过前缀和状态的变化,找到最小的子串长度。
- 输入:字符串
input,数组excess。 - 输出:最小的子串长度
minLen。
通过将问题分解为多个子问题,并抽象出每个子问题的输入和输出,我们可以更清晰地理解问题的结构和解决方法。滑动窗口和前缀和 + 哈希表是两种不同的方法,但它们都通过分解和抽象问题来找到最优解。