【算法专题】滑动窗口:从“无重复字符”到“字母异位词”的深度剖析

【算法专题】滑动窗口:从"无重复字符"到"字母异位词"的深度剖析


我的主页: 寻星探路
个人专栏: 《JAVA(SE)----如此简单!!! 》 《从青铜到王者,就差这讲数据结构!!!》
《数据库那些事!!!》 《JavaEE 初阶启程记:跟我走不踩坑》
《JavaEE 进阶:从架构到落地实战 》 《测试开发漫谈》
《测开视角・力扣算法通关》 《从 0 到 1 刷力扣:算法 + 代码双提升》
《Python 全栈测试开发之路》
没有人天生就会编程,但我生来倔强!!!

寻星探路的个人简介:


1. 题目一:无重复字符的最长子串 (LeetCode 3)

题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

解题思路:变长滑动窗口

这道题的核心在于维护一个不包含重复字符的窗口。

  1. 使用两个指针(左指针 i,右指针 rk)表示窗口边界。
  2. 随着右指针向右移动,将字符存入 HashSet
  3. 如果遇到重复字符,则左指针开始向右移动,并从集合中删除字符,直到窗口内不再有该重复字符。

Java 代码实现

java 复制代码
class Solution {
    public int lengthOfLongestSubstring(String s) {
        // 哈希集合,用于记录窗口内出现的字符
        Set<Character> occ = new HashSet<Character>();
        int n = s.length();
        // 右指针 rk 初始值为 -1,表示在字符串左边界的左侧
        int rk = -1, ans = 0;
        
        for (int i = 0; i < n; i++) {
            if (i != 0) {
                // 每次左指针右移一位,就从集合中移除上一个左边界的字符
                occ.remove(s.charAt(i - 1));
            }
            // 尝试不断移动右指针
            while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
                // 如果下一个字符不重复,加入集合,右指针右移
                occ.add(s.charAt(rk + 1));
                rk++;
            }
            // 更新最大长度:当前窗口为 [i, rk]
            ans = Math.max(ans, rk - i + 1);
        }
        return ans;
    }
}

2. 题目二:找到字符串中所有字母异位词 (LeetCode 438)

题目描述

给定两个字符串 sp,找到 s 中所有 p异位词 的子串,返回这些子串的起始索引。

异位词:由相同字母重排列形成的字符串(字母种类和数量完全一致)。

解题思路:固定长度滑动窗口

因为异位词的长度必须等于 p.length(),所以这是一个固定窗口大小的问题。

  1. 使用数组(或 HashMap)统计 p 中各字母出现的频次。
  2. 维护一个长度为 p.length() 的窗口,统计窗口内 s 的字母频次。
  3. 比较两个频次数组是否相等。若相等,则当前窗口起始索引即为答案。

Java 代码实现

java 复制代码
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int sLen = s.length(), pLen = p.length();
        if (sLen < pLen) return new ArrayList<>();

        List<Integer> ans = new ArrayList<>();
        // 使用 26 位数组记录 a-z 频次,空间复杂度 O(1)
        int[] sCount = new int[26];
        int[] pCount = new int[26];

        // 1. 初始化:统计 p 的频次和 s 第一个窗口的频次
        for (int i = 0; i < pLen; i++) {
            sCount[s.charAt(i) - 'a']++;
            pCount[p.charAt(i) - 'a']++;
        }

        // 检查第一个窗口
        if (Arrays.equals(sCount, pCount)) ans.add(0);

        // 2. 固定长度窗口向右滑动
        for (int i = 0; i < sLen - pLen; i++) {
            sCount[s.charAt(i) - 'a']--;          // 移除左边界字符
            sCount[s.charAt(i + pLen) - 'a']++;   // 移入右边界新字符
            
            // 每次滑动后对比频次
            if (Arrays.equals(sCount, pCount)) {
                ans.add(i + 1);
            }
        }
        return ans;
    }
}

3. 深度拓展:滑动窗口的进阶技巧

优化 1:双数组对比 vs 变量维护

在 438 题中,我们使用了 Arrays.equals,其时间复杂度为 。
进阶写法 :可以维护一个变量 differ 记录两个频次数组中差异的数量。只有当 differ == 0 时才满足条件。这样可以将对比复杂度从 优化到 。

优化 2:ASCII 码的应用

如果题目中字符不仅限于小写字母(如包含空格、数字),应使用 int[128]HashMap<Character, Integer> 来存储频次。


4. 总结:滑动窗口类题目的思考模型

遇到**"子串"、"连续子数组"**等字眼时,应第一时间想到滑动窗口。具体思考流程如下:

  1. 确定窗口类型
  • 固定窗口:窗口大小已知(如 438 题)。只需维护右入左出,并判断窗口状态。
  • 变长窗口:窗口大小随条件变化(如 3 题)。通常是右指针先动寻找可行解,左指针再动寻找最优解。
  1. 确定数据结构
  • 需要判断"是否存在":用 Set
  • 需要统计"出现次数":用 int[26] 数组或 Map
  1. 确定转移逻辑
  • 入窗right 指针移动,更新窗口内状态。
  • 收缩 :什么条件下 left 指针需要右移?
  • 更新 :在什么位置记录结果(ans)?

希望这篇总结对你有帮助!如果你正在准备面试,建议把这两道题对比着多写几遍,体会"左右指针"协同工作的美感。

相关推荐
晚霞的不甘11 小时前
CANN 支持多模态大模型:Qwen-VL 与 LLaVA 的端侧部署实战
人工智能·神经网络·架构·开源·音视频
Vect__12 小时前
基于线程池从零实现TCP计算器网络服务
c++·网络协议·tcp/ip
草履虫建模15 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
华玥作者17 小时前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
AAD5558889917 小时前
YOLO11-EfficientRepBiPAN载重汽车轮胎热成像检测与分类_3
人工智能·分类·数据挖掘
naruto_lnq17 小时前
分布式系统安全通信
开发语言·c++·算法
王建文go17 小时前
RAG(宠物健康AI)
人工智能·宠物·rag
Jasmine_llq17 小时前
《P3157 [CQOI2011] 动态逆序对》
算法·cdq 分治·动态问题静态化+双向偏序统计·树状数组(高效统计元素大小关系·排序算法(预处理偏序和时间戳)·前缀和(合并单个贡献为总逆序对·动态问题静态化
ALINX技术博客17 小时前
【202601芯动态】全球 FPGA 异构热潮,ALINX 高性能异构新品预告
人工智能·fpga开发·gpu算力·fpga
qq_2975746717 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端