目录
滑动窗口算法是一种基于双指针的高效处理技巧,主要用于解决数组或字符串中连续子序列 / 子串的问题。它通过维护一个动态变化的 "窗口"(由左右两个指针界定的连续区间),在遍历过程中灵活调整窗口大小,从而将原本需要嵌套循环(O (n²) 复杂度)的问题优化为线性时间(O (n) 复杂度)。
核心思想
-
窗口定义:由两个指针
left(左边界)和right(右边界)界定的连续区间[left, right]。 -
滑动逻辑
-
先移动
right指针扩展窗口[left, right],直到满足某个条件。 -
再移动
left指针收缩窗口,直到不满足条件,同时更新最优解。 -
重复上述过程,直到
right遍历完整个序列。
-
常见类型及应用场景
滑动窗口主要分为固定大小窗口和可变大小窗口两类:
1. 固定大小窗口
窗口大小固定(如长度为k),常用于求解 " 长度为k的子数组的最大和 / 平均值 " 等问题。
示例:长度为 k 的子数组的最大和
问题:给定数组nums和整数k,找到长度为k的连续子数组的最大和。
解法步骤:
-
初始化窗口:计算前
k个元素的和作为初始窗口和。 -
滑动窗口:从第
k个元素开始,每次移动窗口时,减去离开窗口的元素(nums[i-k]),加上进入窗口的元素(nums[i]),更新最大和。
i 的范围只能从下标0到(array.lenght-1)-k,而j的范围可以从0到array.lenght-1,就这样形成一个为k的大小窗口,左为i,右为j=i+k
public static void maxOf(int[] array,int k){
int sum=0;
for(int i=0;i<=array.length-k;i++){
int sum2=0;
for(int j=i;j<j+k;j++){
sum2+=array[j];
}
sum=Math.max(sum,sum2);
}
System.out.println(sum);
}
示例:找到字符串中所有字母异位词
问题:给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
解法步骤:
-
初始化阶段:创建两个长度为26的数组
sCount和pCount,用于统计字符出现频次,分别统计字符串 s 前plen个字符和字符串 p 所有字符的频次 -
检查初始窗口:比较初始窗口的字符频次是否与目标字符串一致,如果一致,则将位置0加入结果列表
-
滑动窗口遍历:窗口从左向右滑动,每次移动一位,移除窗口左侧字符,添加窗口右侧新字符,比较当前窗口与目标字符串的字符频次
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res=new ArrayList<>();
int slen=s.length();
int plen=p.length();
if(slen<plen){
return res;
}
List<Integer> ans=new ArrayList<>();
int[] sCount=new int[26];
int[] pCount=new int[26];
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);
}
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;
}
}
2. 可变大小窗口
窗口大小不固定,需根据条件动态调整,常用于求解 "最长 / 最短子串 / 子数组" 等问题(如无重复字符的最长子串、最小覆盖子串)。
示例:无重复字符的最长子串
问题:给定一个字符串s,找出其中不含有重复字符的最长子串的长度。

解法步骤:
-
用
left和right标记窗口边界,初始均为 0。 -
移动
right指针,将字符加入窗口,并用哈希表记录字符最后出现的位置。 -
若
right指向的字符已在窗口中(即哈希表记录的位置≥left),则将left移动到该字符上次出现位置的下一位(确保窗口内无重复)。 -
每次移动
right后,更新最长子串长度。
public class T003 {
public static void main(String[] args) {
String s = "abcabcbb";
System.out.println(lengthOfLongestSubstring(s));
}
public static int lengthOfLongestSubstring(String s) {
// 哈希集合,记录每个字符是否出现过
Set<Character> occ = new HashSet<Character>();
int n = s.length();
// 右指针,初始值为 -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;
}
}
滑动窗口的优势
-
时间效率高:将嵌套循环的 O (n²) 复杂度优化为 O (n),只需一次遍历。
-
空间复杂度低:通常只需 O (1) 或 O (k) 的额外空间(
k为字符集大小)。
适用场景总结
滑动窗口适用于连续子序列 / 子串问题,典型场景包括:
-
子数组的最大 / 最小和、平均值(固定窗口)。
-
最长 / 最短子串(如无重复字符、包含特定字符等,可变窗口)。
-
字符串匹配(如最小覆盖子串、找到所有字母异位词)。
核心是通过灵活调整窗口边界,在遍历过程中动态维护窗口状态,避免重复计算,从而高效求解问题。

1万+

被折叠的 条评论
为什么被折叠?



