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

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

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

刀狂发布于 2026/3/28更新于 2026/6/915 浏览
滑动窗口算法实战:从最大连续 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. 最小覆盖子串
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • B/S 架构:现代 Web 应用的核心架构模式
  • 排序算法实战:归并排序的非递归实现
  • Spring Cloud Gateway 动态路由管理平台:Web UI 实时配置
  • 深入剖析 Spring 框架:架构、缺陷与演进之路
  • 开源浪潮下的中国力量:文心一言大模型本地部署与应用全攻略
  • FPGA 实现 CIC 抽取滤波器:原理、位宽计算与 Verilog 实战
  • 使用 AI 工具(Cursor/VS Code)调试 MATLAB 代码实测
  • 2025 年第三季度程序员推荐阅读书单
  • 在 PPT 中嵌入 AI 生成的 H5 代码使用方法
  • UE5.3 C++ ARPG 游戏开发:武器拾取与姿态切换
  • YourKit Java Profiler 性能分析与瓶颈定位实战教程
  • C++ 多态底层原理:V-Table 机制与常见陷阱
  • SDXL Prompt Styler 提示词优化与风格定制实战
  • AI Agent 智能体核心解析与 LangChain 实战
  • AIGC 时代 Python 高效编程学习指南:实战与 AI 辅助
  • FPGA 实现 CIC 抽取滤波器
  • FPGA 组成原理:IO 资源详解
  • Python+AI 学习路线:从入门到实战专家
  • Ubuntu 部署 OpenClaw 实战指南
  • 基于Java构建旅游服务平台的架构与实现

相关免费在线工具

  • 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