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

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


我的主页: 寻星探路
个人专栏: 《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)?

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

相关推荐
程序员小白条1 小时前
面试 Java 基础八股文十问十答第八期
java·开发语言·数据库·spring·面试·职场和发展·毕设
Dxy12393102162 小时前
python连接minio报错:‘SSL routines‘, ‘ssl3_get_record‘, ‘wrong version number‘
开发语言·python·ssl
盈创力和20072 小时前
智慧城市中智能井盖的未来演进:从边缘感知节点到城市智能体
人工智能·智慧城市·智慧市政·智慧水务·智能井盖传感器·综合管廊
njsgcs2 小时前
ppo 找出口模型 训练笔记
人工智能·笔记
大王小生2 小时前
C# CancellationToken
开发语言·c#·token·cancellation
listhi5202 小时前
基于C#实现屏幕放大镜功能
开发语言·c#
萤丰信息2 小时前
从 “钢筋水泥” 到 “数字神经元”:北京 AI 原点社区重构城市进化新逻辑
java·大数据·人工智能·安全·重构·智慧城市·智慧园区
吨吨不打野2 小时前
CS336——2. PyTorch, resource accounting
人工智能·pytorch·python
柳安忆2 小时前
OpenAgents 中文文档总结报告(上手导向版)
人工智能