跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

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

滑动窗口结合哈希表是解决字符串匹配问题的经典方案。针对串联所有单词的子串,将单词视为整体单位,通过固定步长移动窗口并统计频次;对于最小覆盖子串,利用双哈希表或数组动态维护窗口内字符状态,确保包含目标串所有字符且长度最短。两题均通过优化指针移动策略与计数逻辑,在 O(N) 时间复杂度内完成高效求解。

林间仙子发布于 2026/3/24更新于 2026/5/1015 浏览
滑动窗口与哈希表实战:串联所有单词子串与最小覆盖子串

15. 串联所有单词的子串

题目描述

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

示例

输入: s = "barfoothefoobarman" words = ["foo", "bar"] 输出:[0, 9] 解释:从索引 0 开始是 "barfoo",从索引 9 开始是 "foobar"。

解题思路

这道题的核心在于将'单词'视为整体单位进行滑动窗口处理。如果我们将每个单词看作一个字符,问题就转化为了寻找字符串中的异位词组合。

关键差异点:

  • 哈希表的键类型变为 string,用于统计单词频次。
  • 左右指针的移动步长不再是 1,而是单词的长度 len。
  • 由于起始位置可能落在单词中间,我们需要对 0 到 len-1 分别作为起点进行多次遍历。

代码实现

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> ret;
        unordered_map<string, int> hash1; // 统计 words 中所有单词出现的频次
        for(auto& e : words) {
            hash1[e]++;
        }
        
        int len = words[0].size(); // 单词长度
        int m = words.size();      // 单词数量
        int n = s.size();
        
        // 以 0 到 len-1 为起始偏移量分别进行滑动窗口
        for(int i = 0; i < len; i++) {
            unordered_map<string, int> hash2; // 当前窗口内的单词统计
            int count = 0;                    // 窗口内有效单词个数
            int left = i;
            
            // 注意:right + len <= n 防止越界,避免 size_t 与 int 运算导致的负数陷阱
            for(int right = i; right + len <= n; right += len) {
                string str1 = s.substr(right, len);
                hash2[str1]++; 
                
                // 进窗口:如果当前单词在目标集合中且未超标,则计数增加
                if(hash2[str1] <= hash1[str1]) {
                    count++;
                }
                
                // 出窗口:当窗口大小超过所需总长度时,移除左侧单词
                if(right - left + 1 > len * m) {
                    string str2 = s.substr(left, len);
                    if(hash2[str2] <= hash1[str2]) {
                        count--;
                    }
                    hash2[str2]--;
                    left += len;
                }
                
                // 更新结果:当窗口内有效单词数等于目标单词总数
                if(count == m) {
                    ret.push_back(left);
                }
            }
        }
        return ret;
    }
};

流程解析

文章配图 文章配图


16. 最小覆盖子串

题目描述

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在这样的子串,返回空字符串。

示例

输入: s = "ADOBECODEBANC" t = "ABC" 输出:"BANC"

解题思路

这是滑动窗口的经典应用。我们需要维护一个动态窗口,使其包含 t 中的所有字符。

核心逻辑:

  • 使用两个数据结构(哈希表或数组)分别记录目标字符频次和窗口内字符频次。
  • 右指针不断扩展窗口,直到满足条件。
  • 左指针收缩窗口,尝试找到更小的满足条件的解。
  • 优化方案:对于 ASCII 字符,直接使用长度为 128 的数组替代哈希表,性能更佳。

代码实现

class Solution {
public:
    string minWindow(string s, string t) {
        // 优化:使用数组模拟哈希表,提升查找效率
        int hash1[128] = {0}; // 记录目标串 t 的字符频次
        int kinds = 0;        // 记录 t 中不同字符的种类数
        
        for(char c : t) {
            if(hash1[c]++ == 0) {
                kinds++;
            }
        }
        
        int left = 0, 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. 15. 串联所有单词的子串
  2. 题目描述
  3. 示例
  4. 解题思路
  5. 代码实现
  6. 流程解析
  7. 16. 最小覆盖子串
  8. 题目描述
  9. 示例
  10. 解题思路
  11. 代码实现
  12. 流程解析
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python Web 开发:Flask 框架入门与实战
  • C++ 单词翻转:手动实现与标准库对比
  • HTML 网页结构搭建:从语义化标签到整站规划
  • 基于 YOLO11 的无人机航拍小目标检测实战
  • DeepSeek 各版本演进路线与核心差异深度解析
  • 操作系统迁移至新 SSD 的两种实用方法
  • Python 库 Lux 助力 Pandas 智能可视化分析
  • 本地部署 Llama3 指南:使用 Ollama 在个人电脑运行大模型
  • AI Coding 提效实战:从工具选择到思维升级
  • 转行 Python 开发需知的 11 个核心常识
  • 使用闲置 Mac Mini 部署 OpenClaw 打造金融 AI 助手
  • 进阶使用指南 | 即梦AI生图操作技巧解析
  • 文心一言大模型本地部署与微调应用实战
  • Llama-Factory 自定义损失函数实现指南
  • 智能车竞赛惯导与视觉避障实战经验分享
  • ComfyUI 节点式工作流实战:从原理到商业落地
  • Stable Diffusion 提示词编写指南:结构、权重与反向提示词
  • 利用 Prompt 技巧优化简历,提升 AI 筛选通过率
  • Llama Factory GPU 利用率低:算力优化实战部署案例
  • B 站 PC 端自动开启字幕用户脚本

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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