算法日记---滑动窗口
目录
前言
一、滑动窗口基本思路
1.1 什么是窗口
1.2 滑动窗口两种基本类型
1.3算法框架
二、固定大小窗口题目解析
1.1456. 定长子串中元音的最大数目
2.643.子数组最大平均数
三、可变大小窗口
1.3. 无重复字符的最长子串(hot100)
2.438. 找到字符串中所有字母异位词(hot100)
四、
核心思想
总结
前言
滑动窗口算法是处理数组和字符串子问题的高效方法,通过维护一个动态调整的区间(窗口),将时间复杂度从 O (n²) 优化到 O (n)。
本文将用 Java 实现各类滑动窗口问题
一、滑动窗口基本思路
1.1 什么是窗口
我们用生活中的例子进行类比:当你在超市排队结账时,队伍作为一个数组,你所观察的“前三个人”就是一个窗口
- 窗口大小:3个人(固定大小)
- 窗口滑动:当第一个人结账离开,你观察的窗口就像右滑动1位,变成了"第2~4个人"。
1.2 滑动窗口两种基本类型
根据窗口大小是否固定,分为两类:
类型 | 特点 | 应用 |
---|---|---|
固定大小窗口 | 窗口长度始终不变,仅通过右移更新窗口内容 | 求数组中长度为 k 的子数组的最大和,求字符串中长度为 k 的子串的最大元音数 |
可变大小窗口 | 窗口长度可动态扩大或缩小,通过 “右指针扩大窗口、左指针缩小窗口” 调整 | 求最长无重复子串、求满足和≥target 的最短子数组 |
1.3算法框架
无论是上述那种类型,滑动窗口都需要依赖两个核心指针(左指针left,右指针right)来维护窗口
- 初始化:定义
left=0
(窗口左边界),right=0
(窗口右边界),并初始化结果变量(如最大和、最长长度)和窗口状态变量(如当前窗口和、当前窗口内元素集合); - 扩展窗口:移动
right
指针,将新元素加入窗口,更新窗口状态; - 调整窗口:根据问题约束(如窗口大小固定、窗口内元素重复),判断是否需要移动
left
指针缩小窗口,更新窗口状态; - 更新结果:在每次窗口调整后,判断是否满足结果条件,更新最终结果;
- 终止条件:
right
指针遍历完数组 / 字符串。
二、固定大小窗口题目解析
1.1456. 定长子串中元音的最大数目
给你字符串 s
和整数 k
。
请返回字符串 s
中长度为 k
的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a
, e
, i
, o
, u
)。
class Solution {public int maxVowels(String S, int k) {char[] s = S.toCharArray();int ans = 0; // 记录最大元音数int vowel = 0; // 当前窗口的元音数for (int i = 0; i < s.length; i++) { // i为右边界// 入:新字符进入窗口,更新元音计数if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {vowel++;}// 窗口未满(长度 < k)时,只进不出,跳过后续步骤if (i < k - 1) { continue;}// 2. 更新:窗口满了(长度=k),记录当前最大值ans = Math.max(ans, vowel);// 3. 出:左边界字符离开窗口,更新元音计数(为下一次滑动做准备)char out = s[i - k + 1]; // 左边界L = i - k + 1(因为窗口长度为k,L= i -k +1)if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {vowel--;}}return ans;}
}
2.643.子数组最大平均数
给你一个由 n
个元素组成的整数数组 nums
和一个整数 k
。
请你找出平均数最大且 长度为 k
的连续子数组,并输出该最大平均数。
任何误差小于 10-5
的答案都将被视为正确答案。
class Solution {public double findMaxAverage(int[] nums, int k) {double sum = 0;// 先计算第一个窗口的和for (int i = 0; i < k; i++) {sum += nums[i];}// 初始化最大平均值为第一个窗口的平均值double maxAvg = sum / k;// 滑动窗口处理后续子数组for (int i = k; i < nums.length; i++) {// 窗口右边界加入新元素,左边界移除旧元素sum = sum + nums[i] - nums[i - k];// 计算当前窗口平均值,更新最大平均值double currentAvg = sum / k;maxAvg = Math.max(maxAvg, currentAvg);}return maxAvg;}
}
三、可变大小窗口
1.3. 无重复字符的最长子串(hot100)
给定一个字符串 s
,请你找出其中不含有重复字符的 最长 子串 的长度。
class Solution {public int lengthOfLongestSubstring(String s) {// 哈希表:存储字符与其最后出现位置的映射// key: 字符,value: 该字符在字符串中最后出现的索引Map<Character, Integer> charIndex = new HashMap<>();int maxLen = 0; // 记录最长无重复子串的长度,初始为0int left = 0; // 左指针,标记当前窗口起始位置for (int right = 0; right < s.length(); right++) {// 获取当前右指针指向的字符char c = s.charAt(right);// 若字符已存在且索引在当前窗口内,则移动左指针至重复字符的后一项索引if (charIndex.containsKey(c) && charIndex.get(c) >= left) {left = charIndex.get(c) + 1;}charIndex.put(c, right); // 更新字符最新索引maxLen = Math.max(maxLen, right - left + 1);//计算当前窗口长度(right - left + 1)}return maxLen;}
}
2.438. 找到字符串中所有字母异位词(hot100)
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
class Solution {//因为要找 长度固定(和 p 长度相同) 的子串,且需要对比字符频率,所以用 滑动窗口 + 字符频率数组 的思路public List<Integer> findAnagrams(String s, String p) {List<Integer> result=new ArrayList<>();int slen=s.length();int plen=p.length();//固定窗口大小// 边界条件:如果s的长度小于p,不可能存在符合条件的子串,直接返回空列表if(slen<plen){return result;}// 定义两个数组用于统计字符频率(仅适用于小写字母)// 索引0-25分别对应字母a-zint[] sCount = new int[26];int[] pCount = new int[26];//处理s中前p.length()个字符,初始化s和p的频率数组for (int i = 0; i < plen; i++) {//p.charAt(i) - 'a':将字符转换为 0 - 25 的整数(因为题目中是小写字母,a 对应 0,b 对应 1,……,z 对应 25 ),这样就把字符映射到了 pCount 数组的索引上。pCount[p.charAt(i) - 'a']++;sCount[s.charAt(i) - 'a']++;}//初始化窗口状态变量---初始窗口匹配检查(s的前pLen个字符)是否与p是字母异位词if(isEqual(sCount,pCount)){result.add(0);//返回子串起始索引}//滑动窗口遍历剩余字符for(int right=plen;right<slen;right++){//右指针右移,加入新字符sCount[s.charAt(right)-'a']++;// 左指针右移,移除旧字符(窗口左端移出的字符)sCount[s.charAt(right - plen) - 'a']--;// 检查当前窗口是否匹配if (isEqual(sCount, pCount)) {result.add(right - plen + 1);}}return result;}// 辅助方法:判断两个频率数组是否相等private boolean isEqual(int[] a, int[] b) {for (int i = 0; i < 26; i++) {if (a[i] != b[i]) return false;}return true;}
}
四、核心思想
- 维护左右两个指针表示窗口的边界
- 初始化结果变量/初始化窗口状态变量
- 根据问题类型决定窗口是固定大小还是可变大小
- 右指针通常用于扩大窗口,左指针用于缩小窗口
- 每次调整窗口后更新结果
总结
以上即对滑动窗口算法的解析,希望自己能在接下来时间坚持把算法整起来了,不要拖拉了。