最小化替换使字符串字符频次相等
问题描述
小F手中有一个特殊的字符串,这个字符串只包含字符 A、S、D、F,并且长度总是 (4) 的倍数。任务是通过尽可能少的替换,使得这四个字符在字符串中出现的频次相等。
目标是求出使字符串字符频次相等所需替换的 最小子串长度。
解题思路
核心目标
字符串要达到平衡状态,每个字符的频次都应等于 n / 4,其中 (n) 是字符串长度。如果某些字符的频次超过 n / 4,则需要通过替换部分字符来实现平衡。问题的本质可以转化为:找到字符串的一个最小子串,通过替换这个子串中的字符使得剩余部分达到平衡。
这一问题可以用滑动窗口算法高效解决,因为滑动窗口能够快速定位满足条件的最小区间,同时动态调整窗口大小。
详细步骤
1. 统计频次
我们首先统计字符串中每个字符的频次。如果所有字符的频次已经等于 n / 4,说明字符串已经是平衡的,直接返回结果为 (0)。否则,我们将继续寻找一个最小子串,使得替换该子串后,字符串可以达到平衡。
例如,对于字符串 "ASAFASAFADDD":
- 总长度 (n = 12),目标频次为
n / 4 = 3。 - 频次统计结果:
A = 4, S = 3, D = 3, F = 2。 - 可以发现字符
A和F的频次需要调整。
2. 滑动窗口
滑动窗口是解决这类问题的核心工具。它通过动态调整窗口的左右边界,逐步找到满足条件的最优解。
-
初始化:
- 使用两个指针
left和right表示窗口的左右边界,初始均为 (0)。 - 在滑动窗口的过程中,右指针逐步扩展窗口,左指针则在窗口满足条件时尝试收缩窗口。
- 使用两个指针
-
窗口内字符调整:
- 每次右指针向右移动时,将窗口内字符的频次减少,同时检查窗口外的字符频次是否已经满足目标。
- 如果窗口外的字符频次满足平衡条件,尝试记录窗口大小并收缩左指针以进一步优化窗口。
-
收缩窗口:
- 左指针向右移动时,将窗口左端字符重新计入频次,同时保持窗口外部分满足目标。
3. 动态更新最小窗口
在滑动窗口的每次调整中,如果窗口外部分满足平衡条件,就更新最小窗口长度。最终,滑动窗口结束后,记录的最小窗口长度即为答案。
实现代码
以下是 Java 实现代码:
import java.util.HashMap;
public class Main {
public static int solution(String input) {
int n = input.length();
int target = n / 4;
// Frequency map to count occurrences of A, S, D, F
HashMap<Character, Integer> count = new HashMap<>();
count.put('A', 0);
count.put('S', 0);
count.put('D', 0);
count.put('F', 0);
// Count the frequency of each character in the input
for (char ch : input.toCharArray()) {
count.put(ch, count.get(ch) + 1);
}
// If the string is already balanced, no need to replace anything
if (count.get('A') == target && count.get('S') == target &&
count.get('D') == target && count.get('F') == target) {
return 0;
}
// Sliding window approach to find the minimum window size
int left = 0;
int minLength = n;
for (int right = 0; right < n; right++) {
char chRight = input.charAt(right);
count.put(chRight, count.get(chRight) - 1);
// Check if the characters outside the window are already balanced
while (left <= right && count.get('A') <= target &&
count.get('S') <= target && count.get('D') <= target &&
count.get('F') <= target) {
// Update minimum window size
minLength = Math.min(minLength, right - left + 1);
// Shrink the window from the left
char chLeft = input.charAt(left);
count.put(chLeft, count.get(chLeft) + 1);
left++;
}
}
return minLength;
}
public static void main(String[] args) {
// Test cases
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
System.out.println(solution("SSDDFFFFAAAS") == 1);
}
}
示例分析
示例 1
-
输入:
input = "ADDF" -
过程:
-
统计频次:
A=1, S=0, D=2, F=1,目标为n/4 = 1。 -
使用滑动窗口找到最小子串:
- 窗口
[3, 3]包含D,替换后频次平衡。
- 窗口
-
最小子串长度为
1。
-
-
输出:
1
示例 2
-
输入:
input = "ASAFASAFADDD" -
过程:
-
统计频次:
A=4, S=3, D=3, F=2,目标为n/4 = 3。 -
使用滑动窗口找到最小子串:
- 窗口
[8, 10]包含ADD,替换后频次平衡。
- 窗口
-
最小子串长度为
3。
-
-
输出:
3
示例 3
-
输入:
input = "SSDDFFFFAAAS" -
过程:
-
统计频次:
A=3, S=3, D=2, F=4,目标为n/4 = 3。 -
使用滑动窗口找到最小子串:
- 窗口
[8, 8]包含F,替换后频次平衡。
- 窗口
-
最小子串长度为
1。
-
-
输出:
1
时间与空间复杂度
时间复杂度
- 统计频次:遍历字符串一次,复杂度为 O(n)O(n)O(n)。
- 滑动窗口:左右指针各遍历字符串一次,复杂度为 O(n)O(n)O(n)。
- 总时间复杂度为 O(n)O(n)O(n)。
空间复杂度
- 使用了一个
HashMap记录频次,空间复杂度为 O(1)O(1)O(1)(固定大小为 4)。
总结
通过滑动窗口算法,我们能够高效解决此类最小子串问题。关键点在于:
- 通过频次统计找到需要调整的字符。
- 使用滑动窗口逐步缩小范围找到最优解。