优选算法------滑动窗口(概念+解题模板+LeetCode例题讲解)
文章摘要: 本文系统介绍了滑动窗口算法的概念,解题模板,并通过七道经典 LeetCode 例题(长度最小的子数组、无重复字符的最长子串、最大连续1的个数、将X减到0的最小操作数、水果成篮、找出字符串中所有字母异位词、串联所有单词的子串)详细拆解其应用,从而巩固滑动窗口的知识。
目录
滑动窗口简介
1.长度最小的在子数组
2.无重复字符的最长子串
3.最大连续的个数
4.将X减到0的最小操作数
5.水果成篮
6.找出字符串中所有字母异位词
7.串联所有单词的字串
滑动窗口简介
滑动窗口(Sliding Window) 是一种基于同向双指针 的高效算法思想,常用于解决数组或字符串的连续子区间相关问题。
- 核心思想
将问题中的连续区间抽象为一个可以动态伸缩的"窗口",通过移动窗口的左右边界(left和right指针),在 O(n) 时间复杂度内遍历所有可能的子区间,避免暴力枚举的 O(n²) 开销。 - 本质
滑动窗口的本质是同向双指针 ,通过维护窗口的合法性,减少不必要的枚举,将时间复杂度从 O(n²) 优化至 O(n)。 - 适用场景
当你发现暴力解法需要枚举所有子区间(例如子数组、子字符串),且满足以下条件时,可考虑滑动窗口:
*- 问题涉及连续区间。
-
- 可以使用同向双指针 (
left和right均只向右移动,不会后退)。
- 可以使用同向双指针 (
-
- 窗口内的状态(如元素和、字符频次等)可以高效地随指针移动而增量更新。
- 基本步骤(固定模板)
*- 初始化 :
left = 0,right = 0,窗口为[left, right)或[left, right](根据题目定义)。
-
- 进窗口 :将
right指向的元素加入窗口,更新窗口状态(如累加和、计数等)。
- 进窗口 :将
-
- 判断:检查当前窗口是否满足题目要求(如和过大、包含重复字符等)。
-
- 出窗口 :若窗口不满足条件,则移动
left指针缩小窗口,并更新状态。
- 出窗口 :若窗口不满足条件,则移动
-
- 更新结果:在窗口满足条件的时机(可能在进窗口后、出窗口前或循环结束后)记录答案。
-
- 重复步骤 2‑5,直到
right到达数组/字符串末尾。
- 重复步骤 2‑5,直到
- 初始化 :
- 两种常见类型
- 固定长度窗口 :窗口大小固定,每次同时移动
left和right。 - 可变长度窗口 :窗口大小根据条件动态变化,通常用于求最短/最长满足条件的子区间。
- 固定长度窗口 :窗口大小固定,每次同时移动
正文
1.长度最小的在子数组
- 题目链接:长度最小的子数组
- 题目描述:

解法一:暴力枚举
1.定义双指针,left,right,当eft和right之间的数的和大于等于target的时候,记录结果,把所有的结果记录下来
2.最后返回长度最小的数组的长度
注意: 暴力枚举确实记录了所有可能的情况,但我们有必要这样做吗?题目已说明数组元素均为正整数。这意味着,一旦left和right之间的元素和已经大于等于target,若此时继续向右移动right指针,虽然数组和会继续增大,但窗口长度也必然增加。而题目要求的是长度最小的子数组 。因此,在单趟遍历中,当窗口内元素和首次达到或超过target时,就可以立即停止本轮right的扩展,转而通过移动left指针来寻找更短的可行窗口。这正是滑动窗口算法的核心原理:利用数组的单调性(正整数和随窗口扩大而单调递增),避免无效的枚举,将时间复杂度从 O(n²) 优化至 O(n)。
解法二:滑动窗口
1.定义双指针,left,right,left用来固定窗口左端点,right用来固定窗口的右端点
2.数字进入窗口
3.判断窗口内的数字之和是否大于等于target,若大于等于target,记录此时数组长度更新结果,然后出窗口。
4.重复2.3步,直到right遍历完数组
代码 :

2.无重复字符的最长子串
- 题目链接:无重复字符的最长子串
- 题目描述:

- 大概思路:
1.移动右指针,进窗口- 将
right指针指向的字符ss[right]加入当前窗口。
2.判断 :检查该字符是否已存在于哈希集合中。- 如果不存在 :说明当前窗口
[left, right]仍然无重复字符。将该字符加入集合,并更新结果 :maxLen = max(maxLen, right - left + 1)。然后right指针右移一位,继续尝试扩展窗口。 - 如果存在 :说明加入该字符会导致窗口内出现重复字符,窗口不再合法。
3.移动左指针,收缩窗口
- 如果不存在 :说明当前窗口
- 当发现
s[right]已存在于集合中时,需要收缩窗口左侧边界,直到移除这个重复的字符。 - 将
left指针指向的字符s[left]从哈希集合中移除,并将left指针右移一位。 - 重复此步骤(移除
s[left],left++),直到集合中不再包含字符s[right]。此时,窗口[left, right]重新变得合法(无重复字符)。
- 将
- 重复步骤 2 和 3
代码:

3.最大连续的个数
- 题目链接:最大连续1的个数
- 题目描述:

- 大概思路(滑动窗口+zero计数器)
- 初始化 :
left = 0,right = 0,zero = 0,maxLen = 0。 - 进窗口 :
right右移,若nums[right]为0,则zero++。 - 判断与出窗口 :当
zero > k时,移动left缩小窗口,若移出元素为0则zero--。 - 更新结果 :每次窗口合法时 (
zero <= k),更新maxLen = max(maxLen, right - left)。 - 循环 :重复步骤2-4,直至
right遍历完数组,返回maxLen。
- 代码:

4.将X减到0的最小操作数
- 题目链接:将X减到0的最小操作数
- 题目描述:

问题转化:可以将问题转化为,找出最长的子数组的长度len,其中最长子数组的元素和等于sum-x(sum指数组所有元素的和),能找到sum-x这个元素和的子数组,则返回最小操作数;否则返回-1;而nums.length-len即为最小操作数 - 大概思路(滑动窗口):
- 初始化 :
left = 0,right = 0,total = 0(窗口和),target = sum - x。 - 进窗口 :
right右移,total += nums[right]。 - 判断与出窗口 :当
total > target时,移动left缩小窗口,total -= nums[left]。 - 更新结果 :当
total == target时,更新最长子数组长度maxLen = max(maxLen, right - left + 1)。 - 循环 :重复步骤 2-4,直至
right遍历完数组。最终最小操作数为nums.length - maxLen(若maxLen有效)。
- 代码:

5.水果成篮
- 题目链接:水果成篮
- 题目描述:

- 大概思路(滑动窗口 + 哈希表计数):
- 初始化 :
left = 0,right = 0, 哈希Map <Integer,Integer>记录窗口内水果类型及其数量,maxNum = 0。 - 进窗口 :
right右移,将fruits[right]加入 Map。 - 判断与出窗口 :当 Map 中键的数量size(即水果种类)超过 2 时,移动
left缩小窗口:将fruits[left]从 Map中计数减 1,若减到 0 则删除该键,left++,直到种类数 ≤ 2。 - 更新结果 :每次窗口合法(种类数 ≤ 2)时,更新
maxNum = max(maxNum, right - left + 1)。 - 循环 :重复步骤 2-4,直至
right遍历完数组,返回 `maxNum。
- 代码:

6.找出字符串中所有字母异位词
- 题目链接:找出字符串中所有字母异位词
- 题目描述:

- 大概思路(固定长度滑动窗口 + 两个数组模拟哈希):

- 初始化 :
left = 0,right = 0, 两个长度为 26 的数组hash1和hash2分别记录p和当前窗口的字符频次,结果列表list,count记录s中有效字符的数量。 - 预处理 :统计
p的字符频次存入hash1。 - 进窗口 :
right右移,将s[right]加入hash2。若hash2中该字符频次小于等于hash1中的频次,说明此次进入的是有效字符,count++。 - 判断与出窗口 :当窗口长度
(right - left + 1) > p.length()时,移动left缩小窗口:将s[left]从hash2中移除。若移除前hash2中该字符频次小于等于hash1中的频次,说明此次删除的是有效字符,count--。然后left++。 - 更新结果 :当
count ==pp.length(即窗口中每种字符的数量都与p中完全相同)时,说明当前窗口是p的异位词,将left加入结果列表list。 - 循环 :重复步骤 3-5,直至
right遍历完字符串s,返回list。


