当前位置: 首页 > news >正文

【LeetCode 热题 100】(三)滑动窗口

1. 无重复字符的最长字串

class Solution {public int lengthOfLongestSubstring(String s) {int length = s.length();// 1. 定义全局最大长度int ans = 0;// 2. 构建一个滑动窗口HashSet<Character> hashSet = new HashSet<>();int ret = -1;for (int i = 0; i < length; i++) {if(i!=0){hashSet.remove(s.charAt(i-1));}while ((ret+1) < length && !(hashSet.contains(s.charAt(ret+1)))){hashSet.add(s.charAt(ret + 1));ret = ret + 1;}ans = Math.max(ret-i+1, ans);}return ans;}
}

解题思路:滑动窗口法(双指针优化)

关键思想

使用滑动窗口技术维护一个不含重复字符的子串窗口,通过双指针动态调整窗口边界,高效求解最长无重复子串长度。

核心步骤
  1. 初始化组件

    • left指针(代码中的 i):标记窗口起始位置(初始0)
    • right指针(代码中的 ret):标记窗口结束位置(初始-1,表示空窗口)
    • HashSet:存储当前窗口内字符(用于O(1)时间重复判断)
    • maxLength(代码中的 ans):记录全局最大长度(初始0)
  2. 滑动窗口操作

    无重复
    有重复/越界
    开始
    遍历左指针 i
    i>0?
    移除 i-1 位置的字符
    尝试扩展右边界
    右边界可扩展?
    添加字符并右移
    计算当前窗口长度
    更新最大长度
  3. 具体操作解析

    • 左指针移动(每次循环左移1位):
      • i > 0 时,从集合移除 i-1 位置的字符
      • 相当于窗口左边界右移,缩小窗口
    • 右指针扩展
      • right+1 位置字符不在集合中未越界
        • 添加该字符到集合
        • 右指针 right 右移(扩大窗口)
      • 重复直到遇见重复字符或字符串末尾
    • 长度更新
      • 计算当前窗口长度:right - i + 1
      • 更新全局最大值:maxLength = max(当前长度, maxLength)
时间复杂度
  • O(n):左右指针各遍历字符串一次
    • 左指针 i 严格右移 n
    • 右指针 right 只增不减,最多移动 n
  • 每个字符最多被加入/移除集合1次
空间复杂度
  • O(min(n, 字符集大小))
    • 英文字符集:O(26)
    • 全字符集:O(128) 或 O(256)
算法优势
  1. 单次遍历:通过右指针不回退,避免重复检查
  2. 实时更新:每次窗口变动后立即更新最大值
  3. 边界处理:天然支持空串(length=0)、单字符串等边界情况
示例演算(s=“pwwkew”)
左指针i移除字符右指针扩展当前窗口长度最大值
0-p→w→停(w重复)“pw”22
1pw(重复)→停止“w”12
2ww→k→e→停(w重复)“wke”33
3wk(已在)→停止“ke”23
4ke→w→停止“ew”23
5ew(重复)→停止“w”13

最终返回最大值 3(对应子串 "wke"

该实现完美体现了滑动窗口思想:通过动态调整窗口边界,高效维护解空间,在O(n)时间内解决经典子串问题。

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

class Solution {public List<Integer> findAnagrams(String s, String p) {int s_len = s.length();int p_len = p.length();LinkedList<Integer> list = new LinkedList<>();if(s_len < p_len){return list;}int[] sCount = new int[26];int[] pCount = new int[26];for (int i = 0; i < p_len; i++) {int s_index = s.charAt(i) - 'a';sCount[s_index] = sCount[s_index] + 1;int p_index = p.charAt(i) - 'a';pCount[p_index] = pCount[p_index] + 1;}if(Arrays.equals(sCount,pCount)){list.add(0);}for (int i = 0; i < s_len - p_len; i++) {int s_index_1 = s.charAt(i) - 'a';sCount[s_index_1] =  sCount[s_index_1] - 1;int s_index_2 = s.charAt(i + p_len) - 'a';sCount[s_index_2] = sCount[s_index_2] + 1;if(Arrays.equals(sCount,pCount)){list.add(i+1);}}return list;}
}

解题思路:固定长度滑动窗口(字母异位词问题)

问题分析

给定字符串 s 和模式串 p,需要在 s 中找到所有是 p 的字母异位词(anagram)的子串起始索引。字母异位词指字母相同但排列不同的字符串(如 “ab” 和 “ba”)。

核心算法:频率计数+滑动窗口
  1. 边界检查

    • 如果 s 长度小于 p 长度,直接返回空结果(不可能存在异位词)
  2. 频率数组初始化

    • 创建两个长度26的数组(对应26个小写字母):
      • pCount:存储 p 的字符频率
      • sCount:存储 s 的滑动窗口字符频率
    int[] sCount = new int[26];
    int[] pCount = new int[26];
    
  3. 初始化窗口(0位置)

    • 同时统计 psp_len 个字符的频率
    for (int i = 0; i < p_len; i++) {sCount[s.charAt(i) - 'a']++;  // 统计s前p_len个字符pCount[p.charAt(i) - 'a']++;  // 统计p的所有字符
    }
    
    • 检查初始窗口是否匹配:if (Arrays.equals(sCount, pCount)) list.add(0);
  4. 滑动窗口(核心逻辑)

    • 窗口从位置0开始向右滑动:
      for (int i = 0; i < s_len - p_len; i++) {// 1. 移除左边界的字符sCount[s.charAt(i) - 'a']--;// 2. 添加右边界的字符sCount[s.charAt(i + p_len) - 'a']++;// 3. 检查新窗口是否匹配if (Arrays.equals(sCount, pCount)) {list.add(i + 1);  // 记录起始位置}
      }
      
    • 窗口移动演示
      初始窗口:s = [a b a b], p = "ab"
      窗口0: "ab" -> sCount={a:1,b:1} -> 匹配 -> 记录0i=0时:移除s[0]='a':sCount={a:0,b:1}添加s[2]='a':sCount={a:1,b:1}新窗口"ba" -> 匹配 -> 记录1i=1时:移除s[1]='b':sCount={a:1,b:0}添加s[3]='b':sCount={a:1,b:1}新窗口"ab" -> 匹配 -> 记录2
      
关键特性
  1. 固定窗口大小

    • 始终保持窗口长度 = p.length()
    • 通过 i 控制窗口起始位置
  2. 高效频率更新

    • O(1) 时间移除左边界字符
    • O(1) 时间添加右边界字符
    • O(26) 时间比较频率数组(常数时间)
  3. 时间复杂度

    • O(n) :遍历字符串一次(n = s.length())
    • 比暴力解法(O(n²))更高效
示例演示(s=“abab”, p=“ab”)
窗口起始位置窗口内容sCount [a,b]是否匹配结果列表
0“ab”[1,1][0]
1“ba”[1,1][0,1]
2“ab”[1,1][0,1,2]

最终输出:[0,1,2](三个异位词:“ab”、“ba”、“ab”)

算法总结

  1. 适用场景:寻找固定长度的字母异位词
  2. 核心技巧
    • 频率数组代替哈希表(小写字母场景)
    • 滑动窗口避免重复计算
  3. 优势
    • 时间复杂度 O(n)
    • 空间复杂度 O(1)(固定26长度数组)
  4. 扩展性
    • 可处理包含大写/特殊字符的场景(扩大数组长度)
    • 可扩展为找最小覆盖子串等问题
http://www.dtcms.com/a/312252.html

相关文章:

  • 在线任意长度大整数计算器
  • 轻量级鼠标右键增强工具 MousePlus
  • 数据链路层、NAT、代理服务、内网穿透
  • 变频器实习DAY20 测试经验总结
  • WinForm之NumericUpDown控件
  • Noob靶机攻略
  • 力扣刷题日常(11-12)
  • linux编译基础知识-头文件标准路径
  • NX947NX955美光固态闪存NX962NX966
  • FreeRTOS源码分析二:task启动(RISCV架构)
  • 8.苹果ios逆向-安装frida
  • DBMS设计 之1 从DBMS 到数据中台
  • C语言-指针初级(指针定义、指针的作用、指针的计算、野指针、悬空指针、void类型指针)
  • Spring框架深度学习实战
  • ⭐CVPR2025 单目视频深度估计新框架 Seurat
  • 嵌入式系统的中断控制器(NVIC)
  • rosdep的作用以及rosdep install时的常用参数
  • 质数时间(二分查找)
  • ​​​​​​​第二十一天(CDN绕过)
  • EPICS aSub记录示例2
  • [学习笔记-AI基础篇]02_深度基础
  • Kotlin协程极简教程:5分钟学完关键知识点
  • 工业场景工服识别准确率↑32%:陌讯多模态融合算法实战解析
  • OpenVLA复现
  • 23th Day| 39.组合总和,40.组合总和II,131.分割回文串
  • Linux—进程状态
  • 深入 Go 底层原理(九):context 包的设计哲学与实现
  • 智能手表:电源检查
  • Java多线程详解(2)
  • 一、灵巧手捉取几何原理——空间五指平衡捉取