1.1 灵神题单总结_滑动窗口_不定长滑窗

一、定义

窗口左右边界动态伸缩,右边界持续扩张,满足/违反约束时收缩左边界,分最长合法、最短合法、合法计数三类高频场景,是滑动窗口刷题核心难点。

1.1 求最长合法窗口(约束不满足则左移缩窗)

子分类释义
右指针不断右扩,窗口超标时左指针右移收紧,维护符合条件的最大区间长度。

  1. 3 无重复字符的最长子串
    链接:https://leetcode.cn/problems/longest-substring-without-repeating-characters/
  • 题意:找出字符串不含重复字符的最长连续子串长度
  • 考点:不定长窗口+哈希判重、最长区间维护
  1. 904 水果成篮
    链接:https://leetcode.cn/problems/fruit-into-baskets/
  • 题意:最多采摘两种水果,求能摘取的最长连续区间大小
  • 考点:不定长窗口种类计数、最长区间统计
  1. 424 替换后的最长重复字符
    链接:https://leetcode.cn/problems/longest-repeating-character-replacement/
  • 题意:最多替换k个字符,求最长重复连续子串长度
  • 考点:不定长窗口频次最值、可扩容边界判断
  1. 1208 尽可能使字符串相等
    链接:https://leetcode.cn/problems/get-equal-substrings-within-budget/
  • 题意:替换开销不超过预算,求最长可对齐子串长度
  • 考点:不定长窗口累加开销、阈值收缩左边界

1.2 求最短合法窗口(满足条件立刻缩窗更新答案)

子分类释义
右指针扩张至区间达标,持续左移压缩窗口,记录最小合法区间长度。

  1. 209 长度最小的子数组
    链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
  • 题意:数组元素和≥target,求最短连续子数组长度
  • 考点:不定长窗口求和、最小区间更新
  1. 76 最小覆盖子串
    链接:https://leetcode.cn/problems/minimum-window-substring/
  • 题意:s2包含s1全部字符,求s2最短覆盖子串
  • 考点:不定长窗口多字符频次匹配、复杂最短区间维护

1.3 合法窗口计数类(统计所有合规区间总量)

子分类释义
固定扩张逻辑,批量统计满足条件的子数组总个数。

  1. 713 乘积小于K的子数组
    链接:https://leetcode.cn/problems/subarray-product-less-than-k/
  • 题意:统计乘积小于k的连续子数组总数
  • 考点:不定长窗口乘积维护、区间增量计数
  1. 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;
}

四、刷题总结

  1. 最长窗口类:核心是维持窗口合法,不断扩张,记录最大值(无重复子串、水果成篮)
  2. 最短窗口类:窗口合法后持续收缩,极致压缩窗口,记录最小值(最小子数组、最小覆盖子串)
  3. 计数类:利用「固定右端点,统计左区间合法长度」的技巧快速统计子数组数量
  4. 所有题目均满足单调性,是可变滑动窗口的核心判题依据,可直接套模板解题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值