跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

滑动窗口实战:串联所有单词与最小覆盖子串解析

滑动窗口是处理字符串子串问题的利器。本文通过串联所有单词的子串与最小覆盖子串两道经典题目,演示了如何将字符级窗口扩展至单词级,以及利用哈希表统计频次来判定窗口有效性。重点讲解了边界条件处理、类型转换陷阱及数组优化哈希表的技巧,帮助读者掌握此类问题的通用解法。

静心发布于 2026/3/28更新于 2026/10/2584 浏览
滑动窗口实战:串联所有单词与最小覆盖子串解析

滑动窗口实战:串联所有单词与最小覆盖子串

滑动窗口是处理字符串子串问题的利器。本文通过串联所有单词的子串与最小覆盖子串两道经典题目,演示了如何将字符级窗口扩展至单词级,以及利用哈希表统计频次来判定窗口有效性。

15. 串联所有单词的子串

题目描述

给定一个字符串 s 和一些长度相同的单词 words,找出 s 中恰好由 words 中所有单词串联组成的子串的起始索引。

核心思路

这道题的本质可以看作是在寻找字符串中的「单词异位词」。如果我们将每个单词视为一个整体字符,问题就转化为了在长字符串中寻找特定组合的连续片段。

有几个关键点需要注意:

  • 对象粒度:哈希表的键不再是单个字符,而是单词(string)。
  • 步长控制:左右指针的移动步长固定为单词的长度 len。
  • 多轮扫描:由于起始位置可能不同,我们需要以 0 到 len-1 为起点分别进行多次滑动窗口遍历。

代码实现

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> ret;
        unordered_map<string, int> hash1; // 统计目标单词频次
        
        for(auto e : words) {
            hash1[e]++;
        }
        
        int len = words[0].size();
        int m = words.size();
        int left = 0;
        int right = 0;
        
        // 分 len 次遍历,每次从不同的偏移量开始
        for(int i = 0; i < len; i++) {
            unordered_map<string, int> hash2; // 当前窗口内的单词统计
            int count = 0; // 窗口内有效单词的数量
            left = i;
            
            
            
            (right = i; right + len <= ()s.(); right += len) {
                string str1 = s.(right, len);
                hash2[str1]++; 
                
                (hash2[str1] <= hash1[str1]) {
                    count++;
                }
                
                
                (right - left +  > len * m) {
                    string str2 = s.(left, len);
                    (hash2[str2] <= hash1[str2]) {
                        count--;
                    }
                    hash2[str2]--;
                    left += len;
                }
                
                (count == m) {
                    ret.(left);
                }
            }
        }
         ret;
    }
};
// 注意类型转换:s.size() 返回 size_t,与 int 运算需强转
// 避免负数溢出导致越界访问
for
int
size
substr
// 进窗口
if
// 窗口大小超过限制,左移
if
1
substr
if
if
push_back
return

细节解析

在实际运行中,最容易踩坑的是 size() 的类型问题。std::string::size() 返回无符号整数 size_t,如果直接减去 len 可能导致下溢变成极大值。因此代码中显式强转为 int 并采用 right + len <= s.size() 的判断方式更为安全。


16. 最小覆盖子串

题目描述

给定一个字符串 s 和一个字符串 t,找出 s 中包含 t 所有字符的最小子串。如果不存在则返回空串。

核心思路

这是一个典型的动态窗口收缩问题。我们需要维护两个状态:

  1. 目标串 t 中各字符的需求频次。
  2. 当前窗口内已满足的字符频次。

当窗口内包含的字符种类和数量都满足 t 的要求时,尝试收缩左边界以寻找更优解。

优化策略

虽然可以使用 unordered_map 来处理字符映射,但在 ASCII 字符集范围内,使用定长数组(如 int[128])替代哈希表能显著提升性能,减少内存分配开销。

代码实现

class Solution {
public:
    string minWindow(string s, string t) {
        int hash1[128] = { 0 }; // 记录 t 中字符需求
        int kinds = 0;          // 需要匹配的不同字符种类数
        
        for(auto c : t) {
            if(hash1[c]++ == 0) {
                kinds++;
            }
        }
        
        int left = 0;
        int right = 0;
        int count = 0;      // 当前窗口已匹配的字符种类数
        int min_len = s.size() + 1;
        int begin = -1;
        
        int hash2[128] = { 0 }; // 记录当前窗口字符频次
        
        for(right = 0; right < s.size(); right++) {
            hash2[s[right]]++; // 进窗口
            
            // 只有当当前字符数量达到需求时,才增加有效种类计数
            if(hash2[s[right]] == hash1[s[right]]) {
                count++;
            }
            
            // 当所有种类都满足时,尝试收缩窗口
            while(count == kinds) {
                if(min_len > right - left + 1) {
                    min_len = right - left + 1;
                    begin = left;
                }
                
                // 移出左边界字符
                if(hash2[s[left]] == hash1[s[left]]) {
                    count--;
                }
                hash2[s[left]]--;
                left++;
            }
        }
        
        return begin != -1 ? s.substr(begin, min_len) : "";
    }
};

流程总结

  1. 初始化:统计目标串字符频次,确定需要匹配的种类数 kinds。
  2. 扩张右指针:将字符加入窗口,若该字符频次达标,则更新 count。
  3. 收缩左指针:一旦 count == kinds,说明找到可行解。此时记录长度,并尝试移除左侧字符。若移除后某字符不再达标,则停止收缩,继续扩张右指针。
  4. 结果提取:遍历结束后,根据记录的起始位置和长度截取子串。

这种双指针配合数组哈希的方法,时间复杂度为 O(N),空间复杂度为 O(1)(固定 128 大小),是处理此类字符统计问题的标准最优解。

目录

  1. 滑动窗口实战:串联所有单词与最小覆盖子串
  2. 15. 串联所有单词的子串
  3. 题目描述
  4. 核心思路
  5. 代码实现
  6. 细节解析
  7. 16. 最小覆盖子串
  8. 题目描述
  9. 核心思路
  10. 优化策略
  11. 代码实现
  12. 流程总结

更多推荐文章

查看全部
  • C++ STL 无序容器 unordered_set 与 unordered_map 模拟实现
  • 文心大模型 4.5 开源部署指南:架构解析与实战落地
  • baoyu-skills: Claude Code 高效工作流技能集
  • Coze 低代码 AI 智能体平台:100 个落地用途与发布使用指南
  • Java 数组全面解析:定义、内存与工具类
  • 基于 Java 的消息队列选型:RabbitMQ、RocketMQ、Kafka 实战对比
  • 个人健康中枢的 AI 硬件革新与精准健康管理路径
  • Web UI 自动化测试 CI/CD:推送本地代码到 Git 远程仓库
  • Python Pandas Index 常用方法用法精讲
  • AI 产品经理的核心能力与职业转型指南
  • Ollama v0.17.0 更新:OpenClaw 自动安装、Web 搜索、Context 动态分配与 Tokenizer 优化
  • 写给C程序员的C++笔记:类型、输入与运算符
  • 基于 Copilot 的 VS Code 与 Android Studio Android 源码阅读方案
  • 10 款主流降低 AI 检测率工具深度测评与对比
  • RoboMME:机器人通用策略的记忆基准测试与理解
  • C++ 哈希表原理及 unordered_set/map 实现
  • faster-whisper 部署指南:从环境配置到生产级应用
  • 11 篇必读的大模型论文
  • LeetCode 101. 对称二叉树:递归与迭代解法详解
  • GraphQL 在 Python 中的完整实现:从基础到企业级实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online