问题描述
小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
代码
#include <iostream>
#include <string>
#include <unordered_map>
#include <algorithm>
int solution(const std::string& input) {
int n = input.size();
int target = n / 4; // 每个字符的目标频次
std::unordered_map<char, int> freq = {{'A', 0}, {'S', 0}, {'D', 0}, {'F', 0}};
// 统计字符频次
for (char c : input) {
freq[c]++;
}
// 如果已经满足平衡,直接返回 0
if (std::all_of(freq.begin(), freq.end(), [target](const auto& pair) { return pair.second <= target; })) {
return 0;
}
// 滑动窗口寻找最短子串
int min_length = n;
int start = 0;
for (int end = 0; end < n; ++end) {
freq[input[end]]--; // 移入窗口,减少字符频次
// 检查剩余频次是否平衡
while (std::all_of(freq.begin(), freq.end(), [target](const auto& pair) { return pair.second <= target; })) {
min_length = std::min(min_length, end - start + 1); // 更新最短长度
freq[input[start]]++; // 移出窗口左端字符,恢复频次
start++;
}
}
return min_length;
}
int main() {
// 测试用例
std::cout << (solution("ADDF") == 1) << std::endl; // True
std::cout << (solution("ASAFASAFADDD") == 3) << std::endl; // True
std::cout << (solution("SSDDFFFFAAAS") == 1) << std::endl; // True
std::cout << (solution("AAAASSSSDDDDFFFF") == 0) << std::endl; // True
std::cout << (solution("AAAADDDDAAAASSSS") == 4) << std::endl; // True
return 0;
}
代码功能分析
这段代码解决了一个字符频次平衡问题,目标是找到一个字符串的最短子串,移除该子串后,使得字符串中 'A', 'S', 'D', 'F'
的出现频次达到目标值(即 n/4n / 4,其中 nn 是字符串长度)。
详细代码解读
1. 变量定义和频次统计
int n = input.size();
int target = n / 4; // 每个字符的目标频次
std::unordered_map<char, int> freq = {{'A', 0}, {'S', 0}, {'D', 0}, {'F', 0}};
// 统计字符频次
for (char c : input) {
freq[c]++;
}
n
是字符串长度,target
是每个字符应出现的理想频次。- 使用哈希表
freq
存储字符的实际频次。 - 遍历字符串,更新
freq
。
2. 检查是否已经平衡
if (std::all_of(freq.begin(), freq.end(), [target](const auto& pair) { return pair.second <= target; })) {
return 0;
}
- 使用
std::all_of
检查freq
中的每个字符是否已经满足 ≤target\leq target。 - 如果平衡,无需移除任何子串,直接返回
0
。
3. 滑动窗口核心逻辑
int min_length = n;
int start = 0;
for (int end = 0; end < n; ++end) {
freq[input[end]]--; // 移入窗口,减少字符频次
// 检查剩余频次是否平衡
while (std::all_of(freq.begin(), freq.end(), [target](const auto& pair) { return pair.second <= target; })) {
min_length = std::min(min_length, end - start + 1); // 更新最短长度
freq[input[start]]++; // 移出窗口左端字符,恢复频次
start++;
}
}
-
滑动窗口的定义:
start
是窗口的左端点,end
是窗口的右端点。freq[input[end]]--
表示将当前字符计入窗口,减少其在freq
中的频次。
-
窗口内判断:
- 如果移除当前窗口中的子串后,剩余字符串平衡(即所有字符的频次 ≤target\leq target),更新最短子串长度
min_length
。 - 移动
start
,缩小窗口,尝试更短的子串。
- 如果移除当前窗口中的子串后,剩余字符串平衡(即所有字符的频次 ≤target\leq target),更新最短子串长度
-
结果更新:
end - start + 1
表示当前窗口的长度。- 每次发现一个符合条件的窗口,尝试更新
min_length
。
关键知识点
-
滑动窗口:
- 用于高效地解决连续子数组或子串的问题。
- 滑动窗口通过动态调整左右边界,减少不必要的遍历。
-
频次统计:
- 使用
unordered_map
存储字符频次,快速查找和更新。 - 哈希表是频次统计的常用数据结构。
- 使用
-
算法效率:
-
时间复杂度:
- 初始化频次统计:O(n)O(n)。
- 滑动窗口:每个字符最多操作两次(进入和移出窗口),总复杂度为 O(n)O(n)。
-
空间复杂度:
- 仅使用固定大小的哈希表,空间复杂度为 O(1)O(1)。
-
总结
代码总结
- 这段代码通过滑动窗口解决了字符频次平衡问题,是一种高效的实现方式。
- 对于字符串、数组中的子区间问题,滑动窗口提供了一种动态、灵活的解法。
扩展与优化
- 如果字符种类更多或目标字符不固定,可以用同样的方法处理。
- 对于需要更高效的实现场景,可以结合数据结构(如平衡树)优化窗口操作。
生活中的应用
-
字符串处理:
- 这类问题广泛应用于字符串编辑,如文本校验、负载均衡字符串分布等。
-
网络流量监控:
- 在分析时间段内请求频次时,滑动窗口可高效确定负载是否均衡。
-
数据流分析:
- 对连续输入的日志、数据包等进行实时分析,判断某段流量是否异常。
-
资源分配:
- 在服务器中动态调整任务的分布,确保各任务的负载趋于均衡。