C++算法 滑动窗口篇(上)

刷数组和字符串题目时,滑动窗口是绕不开的经典算法。

很多初学者容易把它和暴力双循环混淆,误以为内层 while 循环会带来平方级复杂度;实际上只要左指针不回退,滑动窗口就能做到线性时间。它的精髓在于维护一个动态变化的窗口,不断扩张右边界探索新元素,窗口不满足约束时收缩左边界,始终维持窗口的合法性。有些题目不会直接给出窗口目标,需要我们转换问题视角。接下来将通过四道典型真题,一步步带你理解滑动窗口的套路、模板和使用前提。

目录

一、长度最小的子数组

1.题目

2.核心思路

3.举例模拟

4.代码参考

​5.复杂度

二、无重复字符的最长子串

1.题目

2.核心思路

3.举例模拟

4.代码参考

​5.复杂度

三、最大连续1的个数

1.题目

2.核心思路

3.举例模拟

4.代码参考

​5.复杂度

四、将x减到0的最小操作数

1.题目

2.核心思路

3.举例模拟

4.代码参考

​5.复杂度

五、滑动窗口分类复盘


一、长度最小的子数组

1.题目

2.核心思路

数组全部是正整数,所以窗口右边界右移,和一定变大;左边界右移,和一定变小,可以用滑动窗口。

fast:窗口右指针 ,不断向右扩展窗口,把当前元素加到 sum。

slow:窗口左指针。

每当窗口内总和 sum >= target,说明窗口满足条件:

计算当前窗口长度 fast-slow+1,更新最小长度len;

左指针向右收缩窗口(sum减去左边界的值,slow++),尝试找更小的满足条件窗口;

遍历结束:

如果len仍然是INT_MAX,代表没有找到满足条件的子数组,返回 0;

否则返回最小长度len。

前提:nums 元素都是正数,才能保证收缩窗口时 sum 单调减小。

3.举例模拟

示例:target = 7,nums = [2,3,1,2,4,3] 初始:fast=0, slow=0, sum=0, len=INT_MAX

fast=0:sum=2,sum<7,不进 while

fast=1:sum=5,sum<7,不进 while

fast=2:sum=6,sum<7,不进 while

fast=3:sum=8 ≥7

len = min(INT_MAX,3-0+1)=4

sum=8-2=6,slow=1,sum<7,退出内层 while

fast=4:sum=6+4=10 ≥7

len=min(4,4-1+1)=4

sum=10-3=7,slow=2,sum≥7

len=min(4,4-2+1)=3

sum=7-1=6,slow=3,sum<7,退出内层 while

fast=5:sum=6+3=9 ≥7

len=min(3,5-3+1)=3

sum=9-2=7,slow=4,sum≥7

len=min(3,5-4+1)=2

sum=7-4=3,slow=5,sum<7,退出内层 while

遍历结束,len=2,返回 2。 答案:2

4.代码参考

代码需要引入头文件:

复制代码
#include <vector>
#include <climits>
using namespace std;

5.复杂度

时间复杂度:(O(n)) 每个元素最多被fast指针访问一次,被slow指针访问一次,总共最多 2n 次操作,不是双重循环(O(n^2))。

空间复杂度:(O(1)) 只使用了常数个变量,没有开辟额外数组。


二、无重复字符的最长子串

1.题目

2.核心思路

求无重复字符的最长子串 ,使用左右指针 left、right 维护一个窗口 [left, right],窗口内永远保证没有重复字符:

right 作为右边界不断向右扩张,把当前字符加入哈希数组 hash,hash[c] 记录字符 c 在窗口内出现次数;

如果当前字符 s[right] 的计数大于 1,说明窗口出现重复,循环右移 left,把窗口左边字符移出窗口、计数减一,直到该字符不再重复;

窗口合法后,更新最长长度 ret = max(ret, right-left+1);

right++ 继续向右扫描,直到遍历完整个字符串。

哈希数组大小 180 是为容纳 ASCII 字符,代替unordered_map,访问更快。

3.举例模拟

举例模拟:s = "abcabcbb"

初始:left=0, right=0, ret=0, hash全0

right=0,s0='a',hash'a'=1;hash'a'≤1;ret=1;right=1

right=1,s1='b',hash'b'=1;hash'b'≤1;ret=2;right=2

right=2,s2='c',hash'c'=1;hash'c'≤1;ret=3;right=3

right=3,s 3='a',hash 'a'=2 → 进入内层循环: hash s \[0]-- → hash 'a'=0,left=1;此时 hash 'a'=1,退出循环;窗口 1,3 长度 3;ret 保持 3;right=4

right=4,s 4='b',hash 'b'=2 → 内层循环: hash s \[1]-- → hash 'b'=0,left=2;hash 'b'=1,退出;窗口 2,4 长度 3;ret=3;right=5

right=5,s 5='c',hash 'c'=2 → 内层循环: hash s \[2]-- → hash 'c'=0,left=3;hash 'c'=1,退出;窗口 3,5 长度 3;ret=3;right=6

right=6,s 6='b',hash 'b'=2 → 内层循环: hash s \[3]--,left=4;hash 'b' 依旧 2;继续 hash s \[4]--,left=5;hash 'b'=1;窗口 5,6 长度 2;ret=3;right=7

right=7,s 7='b',hash 'b'=2 → 内层循环: hash s \[5]--,left=6;hash 'b'=1;窗口 6,7 长度 2;ret=3;right=8,循环结束 返回结果:3

4.代码参考

5.复杂度

时间复杂度:O (n) ,n 是字符串长度。right 从 0 走到 n-1 只会走一次;left 只会向右移动,不会回退,总共最多移动 n 次,两层循环合计每个元素最多被访问两次。

空间复杂度:O (1)。哈希数组固定大小 180,和输入字符串长度无关,属于常数空间。


三、最大连续1的个数

1.题目

2.核心思路

这是可变长度滑动窗口(双指针),题目:最多可以把 k 个 0 翻转为 1,求最长连续 1 的子数组长度。

右指针 right 不断向右扩张窗口;遇到 0,窗口内 0 计数器 count++。

当窗口内 0 的数量 count > k,窗口非法:移动左指针 left 收缩窗口:

nums[left++] == 0:如果左边移出的元素是 0,count--。

收缩到 count ≤ k 时,窗口合法,更新窗口最大长度 ret = max(ret, right-left+1)。

right++,继续向右探索。

窗口定义:[left, right],窗口内 0 的个数 ≤ k,记录合法窗口的最大长度。

3.举例模拟

样例:nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2 初始:left=0, right=0, count=0, ret=0

right=0,num=1 → count=0;窗口 0,0,len=1,ret=1;right=1

right=1,num=1 → count=0;窗口 0,1,len=2,ret=2;right=2

right=2,num=1 → count=0;窗口 0,2,len=3,ret=3;right=3

right=3,num=0 → count=1;窗口 0,3,len=4,ret=4;right=4

right=4,num=0 → count=2;窗口 0,4,len=5,ret=5;right=5

right=5,num=0 → count=3,count>k=2,进入收缩:

nums left=0=1,left 变成 1,count 不变

nums left=1=1,left 变成 2,count 不变

nums left=2=1,left 变成 3,count 不变

nums left=3=0,left 变成 4,count=2 ✔退出内层循环 窗口 4,5,长度 2,ret 保持 5;right=6

right=6,num=1,count=2;窗口 4,6 len=3;right=7

right=7,num=1,窗口 4,7 len=4;right=8

right=8,num=1,窗口 4,8 len=5;right=9

right=9,num=1,窗口 4,9 len=6 → ret=6;right=10

right=10,num=0 → count=3>2,收缩: nums left=4=0 → left=5,count=2 ✔退出循环 窗口 5,10 len=6,ret 依旧 6;right=11,循环结束 最终返回 6

4.代码参考

5.复杂度

  • 时间复杂度 O (n):left 和 right 都只会从 0 走到 n-1,每个元素最多被左右指针各访问一次,没有嵌套循环叠加。
  • 空间复杂度 O (1):只使用了 left、right、count、ret 几个临时变量,额外空间常数级,不随数组大小变化。

四、将x减到0的最小操作数

1.题目

2.核心思路

只能从数组最左边、最右边拿数字 ,拿出来数字总和等于 x,求最少拿多少次;做不到返回 -1。

直接拿左右两边不好枚举,做转化:

拿左右若干数和为 x ⇨ 中间剩下一段连续子数组 ,这段子数组的和 = sum(nums) - x 想要拿的次数最少 ⇨ 中间保留的连续子数组尽可能长

前提:本题元素全是正整数,所以可以滑动窗口(双指针)。

先算数组总和 sum,目标子数组和 tar = sum - x

如果 tar < 0:说明总和都不够 x,直接返回 - 1

滑动窗口找和等于 tar 的最长连续子数组

找到最长长度 ret,答案就是 n - ret(总长度减去中间保留长度 = 左右拿走的数量)

遍历完都没找到和为 tar 的子数组,返回 - 1

3.举例模拟

样例:nums = [1,1,4,2,3], x = 5

sum = 1+1+4+2+3 = 11

tar = 11 - 5 = 6,我们要找和 = 6 的最长连续子数组

n = 5

窗口初始:left=0, tmp=0

right=0,tmp +=1 → tmp=1,tmp<6

right=1,tmp +=1 → tmp=2,tmp<6

right=2,tmp +=4 → tmp=6 ,窗口 0,2,长度 3,ret=3

right=3,tmp +=2 → tmp=8 >6

tmp -= nums left=0 → tmp=7,left=1,仍然 > 6

tmp -= nums left=1 → tmp=6 ,窗口 2,3,长度 2,ret 保持 max (3,2)=3

right=4,tmp +=3 → tmp=9>6

tmp -= numsleft=2 → tmp=5,left=3,tmp<6

遍历结束,ret=3 答案 = 5 - 3 = 2 解释:中间保留 1,4,2(和 6),左右拿走 1、3,一共 2 次,符合题意。

补充另一个样例:nums=5,6,7,8,9,x=4,sum=35, tar=31,找不到和 31 的子数组,返回 - 1

4.代码参考

5.复杂度

时间复杂度:(O(n)) left 和 right 都只会向右移动,每个元素最多被加入窗口一次、移出窗口一次,没有嵌套循环。

空间复杂度:(O(1)) 只用了几个 int 变量,没有额外数组 / 哈希。


五、滑动窗口分类复盘

最短窗口模板:右指针扩窗口,满足条件就持续收缩左指针,不断更新最小值(长度最小的子数组)

最长窗口模板:右指针扩窗口,窗口非法时收缩左指针,合法后更新最大值(无重复最长子串、最多翻转 k 个 0、最小操作数)

关键限制:纯正数组才可以用双指针滑动窗口做子数组和类题目;有负数时不能直接双指针,要改用前缀和 + 哈希

复杂度本质:left、right 单向向右,每个元素最多被遍历两次,不是(O(n^2))

相关推荐
Ivanqhz1 小时前
超参数考量与设计
数据库·人工智能·深度学习·算法·机器学习
像枫一样1 小时前
STM32串口轮询收发实战教程
arm开发·c++·stm32·单片机·嵌入式硬件·fpga开发·51单片机
估值探索者1 小时前
【Python量化技术指标实战 04】布林带通道突破,怎么知道是真突破还是假突破?
java·开发语言·python·接口·股票数据api接口
Soneswy1 小时前
C++基础篇——类与对象(上)
开发语言·c++
张小姐的猫1 小时前
【MySQL】表的约束
android·linux·服务器·数据库·c++·mysql·adb
吞下星星的少年·-·1 小时前
Div.2 CF edu195A题(数学,贪心)
数学·算法·贪心
kiracrimson1 小时前
STL简单引入:把数据结构做成模板
开发语言·数据结构·c++
fanruitian1 小时前
python property
开发语言·python
罗湖老棍子1 小时前
【例 2】牧场的安排(信息学奥赛一本通- P1593)三角形牧场(洛谷-P1284)
算法·动态规划·状压dp·状态压缩动态规划