最小一环字串长度 | 豆包MarsCode AI刷题

4 阅读4分钟

问题描述

小F得到了一个特殊的字符串,这个字符串只包含字符ASDF,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得ASDF这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。


测试样例

样例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,缩小窗口,尝试更短的子串。
  • 结果更新

    • end - start + 1 表示当前窗口的长度。
    • 每次发现一个符合条件的窗口,尝试更新 min_length

关键知识点

  1. 滑动窗口

    • 用于高效地解决连续子数组或子串的问题。
    • 滑动窗口通过动态调整左右边界,减少不必要的遍历。
  2. 频次统计

    • 使用 unordered_map 存储字符频次,快速查找和更新。
    • 哈希表是频次统计的常用数据结构。
  3. 算法效率

    • 时间复杂度

      • 初始化频次统计:O(n)O(n)。
      • 滑动窗口:每个字符最多操作两次(进入和移出窗口),总复杂度为 O(n)O(n)。
    • 空间复杂度

      • 仅使用固定大小的哈希表,空间复杂度为 O(1)O(1)。

总结

代码总结

  • 这段代码通过滑动窗口解决了字符频次平衡问题,是一种高效的实现方式。
  • 对于字符串、数组中的子区间问题,滑动窗口提供了一种动态、灵活的解法。

扩展与优化

  • 如果字符种类更多或目标字符不固定,可以用同样的方法处理。
  • 对于需要更高效的实现场景,可以结合数据结构(如平衡树)优化窗口操作。

生活中的应用

  1. 字符串处理

    • 这类问题广泛应用于字符串编辑,如文本校验、负载均衡字符串分布等。
  2. 网络流量监控

    • 在分析时间段内请求频次时,滑动窗口可高效确定负载是否均衡。
  3. 数据流分析

    • 对连续输入的日志、数据包等进行实时分析,判断某段流量是否异常。
  4. 资源分配

    • 在服务器中动态调整任务的分布,确保各任务的负载趋于均衡。