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

滑动窗口算法实战:从最大连续 1 到最小覆盖子串

滑动窗口算法通过维护动态区间高效解决字符串与数组的子序列问题。本文结合四个经典 LeetCode 例题,演示如何利用双指针优化暴力枚举。涵盖最大连续 1 的个数、字母异位词查找、单词串联子串及最小覆盖子串等场景。重点讲解如何定义进出窗口条件、维护计数变量以及处理边界情况,提供完整的 Java 实现代码与复杂度分析,帮助读者掌握滑动窗口的核心思维模式。

刀狂发布于 2026/3/28更新于 2026/7/727 浏览
滑动窗口算法实战:从最大连续 1 到最小覆盖子串

文章配图

文章配图

一、例题精讲

1.1. 最大连续 1 的个数 III

题目给定一个二进制数组,最多允许翻转 k 个 0。注意是'最多'而非'恰好',如果数组中 0 的总数小于 k,则无需额外操作。

直接模拟翻转过程代码会显得繁琐。我们可以转换思路:寻找一个最长的子数组,其中包含的 0 的个数不超过 k。这样就不需要真正去修改数组元素。

暴力解法容易想到:固定起点向右扩展,统计 0 的数量,一旦超过 k 就停止。但这会导致时间复杂度较高。优化后的做法是使用同向双指针(滑动窗口)。

定义 left 和 right 指针,初始指向数组头部。right 负责扩张窗口,遇到 0 时计数器加一;当 0 的个数超过 k 时,left 开始收缩窗口,直到满足条件。在这个过程中,right 不需要回退,这是滑动窗口的核心优势。

步骤概括为:进窗口 → 判断条件 → 出窗口 → 更新结果。

public class Solution {
    public int longestOnes(int[] nums, int k) {
        int ret = 0;
        int zero = 0;
        for (int left = 0, right = 0; right < nums.length; right++) {
            if (nums[right] == 0) {
                zero++;
            }
            while (zero > k) {
                if (nums[left++] == 0) {
                    zero--;
                }
            }
            ret = Math.max(ret, right - left + );
        }
         ret;
    }
}
1
return

1.2. 找到字符串中所有字母异位词

以示例 1 为例,p 可以重排为 abc、acb 等。我们需要在 s 中找到所有与 p 互为异位词的子串起始索引。

判断异位词的核心是字符频次一致。暴力解法是每次截取长度相同的子串重新统计哈希表,效率较低。优化的关键在于利用滑动窗口:窗口大小固定为 p 的长度。

维护两个计数数组(或小哈希表),分别记录 p 的字符频次和当前窗口的字符频次。为了进一步优化,引入变量 count 统计'有效匹配字符数'。当窗口内某字符数量达到 p 中该字符的数量时,count 增加;移出窗口时相应减少。当 count 等于 p 的长度时,说明找到了一个异位词。

由于题目限定小写字母,使用长度为 26 的整型数组比 HashMap 更高效,时间复杂度可降至 O(n)。

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ret = new ArrayList<>();
        char[] ss = s.toCharArray();
        char[] pp = p.toCharArray();
        int[] hash1 = new int[26];
        for (char ch : pp) {
            hash1[ch - 'a']++;
        }
        int[] hash2 = new int[26];
        int count = 0;
        for (int left = 0, right = 0; right < s.length(); right++) {
            char in = ss[right];
            if (++hash2[in - 'a'] <= hash1[in - 'a']) {
                count++;
            }
            if (right - left + 1 > p.length()) {
                char out = ss[left++];
                if (hash2[out - 'a']-- <= hash1[out - 'a']) {
                    count--;
                }
            }
            if (count == p.length()) {
                ret.add(left);
            }
        }
        return ret;
    }
}

1.3. 串联所有单词的子串

这道题要求找出 s 中包含 words 数组中所有单词(每个单词出现次数需一致)的子串。

与上一题类似,但处理对象变成了单词而非字符。这里需要使用 Map 来映射单词及其频次。关键点在于指针的移动步长:right 指针不应每次移动一个字符,而应移动 words 中单词的长度 len。

此外,由于起始位置不同可能导致匹配失败,我们需要对 s 进行多次滑动窗口遍历,起始偏移量从 0 到 len-1 即可覆盖所有情况。这样避免了重复计算,保证了效率。

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> ret = new ArrayList<>();
        Map<String, Integer> hash1 = new HashMap<>();
        for (String str : words) {
            hash1.put(str, hash1.getOrDefault(str, 0) + 1);
        }
        int len = words[0].length(), m = words.length;
        for (int i = 0; i < len; i++) {
            Map<String, Integer> hash2 = new HashMap<>();
            int count = 0;
            for (int left = i, right = i; right + len <= s.length(); right += len) {
                String in = s.substring(right, right + len);
                hash2.put(in, hash2.getOrDefault(in, 0) + 1);
                if (hash2.get(in) <= hash1.getOrDefault(in, 0)) {
                    count++;
                }
                if (right - left + 1 > len * m) {
                    String out = s.substring(left, left + len);
                    if (hash2.get(out) <= hash1.getOrDefault(out, 0)) {
                        count--;
                    }
                    hash2.put(out, hash2.get(out) - 1);
                    left += len;
                }
                if (count == m) {
                    ret.add(left);
                }
            }
        }
        return ret;
    }
}

1.4. 最小覆盖子串

目标是在 s 中找到包含 t 所有字符的最短子串。若不存在则返回空字符串。

同样采用滑动窗口策略。right 指针扩张直到窗口包含 t 的所有字符(即满足覆盖条件),此时尝试收缩 left 指针以缩短窗口长度,同时保持覆盖条件成立。记录过程中的最小长度及起始位置。

为了快速判断是否满足条件,我们不再每次都遍历哈希表比较,而是维护一个变量 kinds 记录 t 中不同字符的种类数,以及 count 记录当前窗口中已满足频次要求的字符种类数。当 count 等于 kinds 时,说明窗口合法。

注意边界处理,如 begin 未更新时返回空串。

public class Solution {
    public String minWindow(String s, String t) {
        char[] ss = s.toCharArray();
        char[] tt = t.toCharArray();
        int[] hash1 = new int[128];
        int kinds = 0;
        for (char ch : tt) {
            if (hash1[ch]++ == 0) {
                kinds++;
            }
        }
        int[] hash2 = new int[128];
        int minlen = Integer.MAX_VALUE;
        int begin = -1;
        for (int left = 0, right = 0, count = 0; right < ss.length; right++) {
            char in = ss[right];
            if (++hash2[in] == hash1[in]) {
                count++;
            }
            while (kinds == count) {
                if (right - left + 1 < minlen) {
                    begin = left;
                    minlen = right - left + 1;
                }
                char out = ss[left++];
                if (hash2[out]-- == hash1[out]) {
                    count--;
                }
            }
        }
        if (begin == -1) {
            return "";
        } else {
            return s.substring(begin, begin + minlen);
        }
    }
}

目录

  1. 一、例题精讲
  2. 1.1. 最大连续 1 的个数 III
  3. 1.2. 找到字符串中所有字母异位词
  4. 1.3. 串联所有单词的子串
  5. 1.4. 最小覆盖子串
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 性能测试、敏捷开发与全排列算法实战
  • Coze 抓取小红书视频并写入飞书多维表实战指南
  • C++ 实现 JSON 与 HTTP 协议,从零构建 Web 计算器服务器
  • Android 组件化架构设计与实现方案详解
  • C++ 入门:命名空间(namespace)详解
  • Java 性能调优实战:JMH 基准测试与 JProfiler/Async-Profiler 剖析
  • 青海少年 AI 农业创业:基于 ViT 的病虫害检测系统
  • Flutter 三方库 xpath_selector 在鸿蒙系统的适配与应用
  • DALL·E 3 绘图功能与 API 使用指南
  • C++ 继承:面向对象代码复用的核心机制
  • 新兴人工智能 Agent 架构综述:推理、规划与工具调用
  • 滑动窗口算法实战:串联所有单词的子串与最小覆盖子串
  • ICCV2019 论文录取数据分析:华人表现与机构统计
  • 基于 DeepFace 与 OpenCV 的实时情绪分析实战
  • C语言与C++的核心区别详解
  • Java 实现双向链表:LinkedList 模拟与源码解析
  • 新手如何从零开始学习漏洞挖掘与渗透测试
  • C++ 图论:三种经典最短路径算法解析
  • 搜索引擎收录加速与外链建设策略
  • Sambert-HifiGan 在智能家居中控的语音反馈设计
  • 相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • 加密/解密文本

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

    • Gemini 图片去水印

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