一、定义
窗口左右边界动态伸缩,右边界持续扩张,满足/违反约束时收缩左边界,分最长合法、最短合法、合法计数三类高频场景,是滑动窗口刷题核心难点。
1.1 求最长合法窗口(约束不满足则左移缩窗)
子分类释义
右指针不断右扩,窗口超标时左指针右移收紧,维护符合条件的最大区间长度。
- 3 无重复字符的最长子串
链接:https://leetcode.cn/problems/longest-substring-without-repeating-characters/
- 题意:找出字符串不含重复字符的最长连续子串长度
- 考点:不定长窗口+哈希判重、最长区间维护
- 904 水果成篮
链接:https://leetcode.cn/problems/fruit-into-baskets/
- 题意:最多采摘两种水果,求能摘取的最长连续区间大小
- 考点:不定长窗口种类计数、最长区间统计
- 424 替换后的最长重复字符
链接:https://leetcode.cn/problems/longest-repeating-character-replacement/
- 题意:最多替换k个字符,求最长重复连续子串长度
- 考点:不定长窗口频次最值、可扩容边界判断
- 1208 尽可能使字符串相等
链接:https://leetcode.cn/problems/get-equal-substrings-within-budget/
- 题意:替换开销不超过预算,求最长可对齐子串长度
- 考点:不定长窗口累加开销、阈值收缩左边界
1.2 求最短合法窗口(满足条件立刻缩窗更新答案)
子分类释义
右指针扩张至区间达标,持续左移压缩窗口,记录最小合法区间长度。
- 209 长度最小的子数组
链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
- 题意:数组元素和≥target,求最短连续子数组长度
- 考点:不定长窗口求和、最小区间更新
- 76 最小覆盖子串
链接:https://leetcode.cn/problems/minimum-window-substring/
- 题意:s2包含s1全部字符,求s2最短覆盖子串
- 考点:不定长窗口多字符频次匹配、复杂最短区间维护
1.3 合法窗口计数类(统计所有合规区间总量)
子分类释义
固定扩张逻辑,批量统计满足条件的子数组总个数。
- 713 乘积小于K的子数组
链接:https://leetcode.cn/problems/subarray-product-less-than-k/
- 题意:统计乘积小于k的连续子数组总数
- 考点:不定长窗口乘积维护、区间增量计数
- 930 和相同的二元子数组
链接:https://leetcode.cn/problems/binary-subarrays-with-sum/
- 题意:仅含0/1数组,求和等于目标值的子数组数量
- 考点:不定长窗口前缀和、双边界计数优化
二、整体释义
1. 算法本质
可变滑动窗口基于同向双指针(left、right),是处理连续子数组/子串问题的最优线性解法。核心逻辑:右指针持续向右扩张窗口,当窗口不满足题目合法条件时,循环右移左指针收缩窗口,直至窗口重新合法,全程仅遍历数组一次,时间复杂度严格 O(n)。
2. 适用条件(单调性)
窗口区间具备单向单调性:
- 右指针右移:窗口范围扩大,指标(和、乘积、字符种类、水果种类)单向增大/不变
- 左指针右移:窗口范围缩小,指标单向减小/不变
正整数数组、字符统计类题目天然满足该特性,可无脑使用可变滑动窗口。
3. 通用解题模板
初始化left=0, 统计变量, 结果变量
for(right = 0; right < 数组长度; right++){
1. 右指针元素入窗,更新窗口统计信息
2. while(窗口不合法){
左指针元素出窗,更新统计信息
left++
}
3. 窗口合法,更新最终答案(最大长度/最小长度/计数)
}
4. 题型分类
- 最长合法窗口:无重复子串、最多两种元素子数组
- 最短合法窗口:最小子数组和、最小覆盖子串
- 合法窗口计数:乘积小于K的子数组、二元子数组求和
三、经典题目代码实现(C语言)
1. LeetCode 3. 无重复字符的最长子串
题目链接:https://leetcode.cn/problems/longest-substring-without-repeating-characters/
难度:中等
题目描述:给定一个字符串 s ,找出其中不含有重复字符的最长子串的长度。子串为连续字符序列,子序列不合法。
核心思路:利用ASCII数组记录窗口内字符是否存在,右指针扩张窗口,遇到重复字符则持续收缩左边界,清除重复标记,实时更新最大窗口长度。
示例:
- 输入: s = “abcabcbb” 输出: 3
- 输入: s = “bbbbb” 输出: 1
- 输入: s = “pwwkew” 输出: 3
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// 无重复字符的最长子串
int lengthOfLongestSubstring(char *s)
{
int ans = 0;
int l = 0;
// ASCII 0~128 标记字符是否存在于当前窗口
bool has[128] = {false};
int n = strlen(s);
for (int r = 0; r < n; r++) {
char c = s[r];
// 当前字符已存在窗口,持续收缩左边界直到删掉重复字符
while (has[c] == true) {
has[s[l]] = false;
l++;
}
has[c] = true;
int len = r - l + 1;
if (len > ans) {
ans = len;
}
}
return ans;
}
2. LeetCode 904. 水果成篮
题目链接:https://leetcode.cn/problems/fruit-into-baskets
难度:中等
题目描述:数组元素代表水果种类,仅有两个篮子,每个篮子装单一水果,只能连续向右采摘,遇到第三种水果停止,求最多采摘水果数量。等价于:最多包含两种数字的最长连续子数组长度。
核心思路:滑动窗口统计窗口内水果种类与数量,右指针扩张,种类超过2时收缩左边界,始终维持窗口合法,更新最大窗口长度。
示例:
- 输入:fruits = [1,2,1] 输出:3
- 输入:fruits = [0,1,2,2] 输出:3
- 输入:fruits = [1,2,3,2,2] 输出:4
#include <stdio.h>
// 水果成篮:最多两种元素的最长子数组
int totalFruit(int *fruits, int fruitsSize)
{
int window[100005] = {0};
int left = 0;
int kind = 0;
int maxLen = 0;
for (int right = 0; right < fruitsSize; right++) {
int cur = fruits[right];
if (window[cur] == 0) {
kind++;
}
window[cur]++;
// 种类超过2,收缩左边界维持窗口合法
while (kind > 2) {
int lVal = fruits[left];
window[lVal]--;
if (window[lVal] == 0) {
kind--;
}
left++;
}
// 更新最大窗口长度
int winLen = right - left + 1;
if (winLen > maxLen) {
maxLen = winLen;
}
}
return maxLen;
}
3. LeetCode 209. 长度最小的子数组
题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum
难度:中等
题目描述:给定正整数数组与目标值target,找出总和 ≥ target 的最短连续子数组,无合法子数组返回0。数组全为正数,窗口和严格单调。
核心思路:右指针累加窗口和,当和满足条件时,持续收缩左边界,不断刷新最小合法窗口长度,利用正数组单调性保证解法正确。
示例:
- 输入:target = 7, nums = [2,3,1,2,4,3] 输出:2
- 输入:target = 4, nums = [1,4,4] 输出:1
- 输入:target = 11, nums = [1,1,1,1] 输出:0
#include <stdio.h>
#include <limits.h>
// 长度最小的子数组
int minSubArrayLen(int target, int *nums, int numsSize)
{
int minLen = INT_MAX;
int left = 0;
int sum = 0;
for (int right = 0; right < numsSize; right++) {
sum += nums[right];
// 满足条件则收缩左边界,寻找更短窗口
while (sum >= target) {
int curLen = right - left + 1;
if (curLen < minLen) {
minLen = curLen;
}
sum -= nums[left];
left++;
}
}
return minLen == INT_MAX ? 0 : minLen;
}
4. LeetCode 76. 最小覆盖子串
题目链接:https://leetcode.cn/problems/minimum-window-substring
难度:困难
题目描述:给定字符串 s 和 t,返回 s 中包含 t 全部字符(含重复次数)的最短连续子串,无合法子串返回空串。
核心思路:双频次数组统计目标字符需求与窗口字符数量,通过valid变量统计匹配成功的字符种类,窗口完全匹配后收缩左边界,刷新最小窗口,最后截取答案子串。
示例:
- 输入:s = “ADOBECODEBANC”, t = “ABC” 输出:“BANC”
- 输入:s = “a”, t = “a” 输出:“a”
- 输入:s = “a”, t = “aa” 输出:“”
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#define ASCII_MAX 128
// 最小覆盖子串
char *minWindow(char *s, char *t)
{
int need[ASCII_MAX] = {0};
int window[ASCII_MAX] = {0};
int tLen = strlen(t);
int sLen = strlen(s);
int valid = 0;
int minStart = 0;
int minLen = INT_MAX;
// 前置剪枝
if (tLen > sLen) {
char *empty = (char *)malloc(1);
empty[0] = '\0';
return empty;
}
// 统计目标字符需求数量
for (int i = 0; i < tLen; i++) {
need[t[i]]++;
}
int j = 0;
for (int i = 0; i < sLen; i++) {
char r = s[i];
window[r]++;
// 有效匹配字符数累加
if (window[r] <= need[r]) {
valid++;
}
// 窗口完全匹配,收缩左边界
while (valid == tLen) {
int curLen = i - j + 1;
if (curLen < minLen) {
minLen = curLen;
minStart = j;
}
char l = s[j];
if (window[l] <= need[l]) {
valid--;
}
window[l]--;
j++;
}
}
// 无合法窗口
if (minLen == INT_MAX) {
char *empty = (char *)malloc(1);
empty[0] = '\0';
return empty;
}
// 截取结果子串
char *ans = (char *)malloc(minLen + 1);
strncpy(ans, s + minStart, minLen);
ans[minLen] = '\0';
return ans;
}
5. LeetCode 713. 乘积小于 K 的子数组
难度:中等
题目描述:给定正整数数组 nums 和整数 k,统计所有元素乘积严格小于 k 的连续子数组数量。
核心思路:正数组乘积单调递增,右指针更新窗口乘积,乘积超标则收缩左边界;固定右端点,合法左区间长度即为新增合法子数组数量,特判k≤1时直接返回0。
示例:
- 输入:nums = [10,5,2,6], k = 100 输出:8
- 输入:nums = [1,2,3], k = 0 输出:0
#include <stdio.h>
// 乘积小于K的子数组数量
int numSubarrayProductLessThanK(int *nums, int numsSize, int k)
{
if (k <= 1) {
return 0;
}
int left = 0;
long long product = 1; // 防止乘积溢出
int ans = 0;
for (int right = 0; right < numsSize; right++) {
product *= nums[right];
// 乘积超标,收缩左边界
while (product >= k) {
product /= nums[left];
left++;
}
// 累加当前右端点的所有合法子数组
ans += right - left + 1;
}
return ans;
}
四、刷题总结
- 最长窗口类:核心是维持窗口合法,不断扩张,记录最大值(无重复子串、水果成篮)
- 最短窗口类:窗口合法后持续收缩,极致压缩窗口,记录最小值(最小子数组、最小覆盖子串)
- 计数类:利用「固定右端点,统计左区间合法长度」的技巧快速统计子数组数量
- 所有题目均满足单调性,是可变滑动窗口的核心判题依据,可直接套模板解题

1774

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



