滑动窗口五题通关:从食堂打饭到上下文窗口——算法与大模型的内存管理哲学

【学习记录】滑动窗口五题通关:从食堂打饭到上下文窗口------算法与大模型的内存管理哲学

在经历了"两数之和"的哈希表思维和"跳跃游戏"的贪心思维之后,我们来到了算法面试中另一个举足轻重的专题------滑动窗口(Sliding Window) 。如果说哈希表是"空间换时间"的典范,贪心是"局部最优→全局最优"的策略,那么滑动窗口就是 "双指针协同移动" 的极致艺术。今天,我们用"食堂打饭窗口"的比喻,把五道经典的滑动窗口题目串在一起讲透------从最基础的子数组求和,到需要哈希表辅助的最小覆盖子串,再到固定长度的异位词检测。读完这篇,你会对滑动窗口产生肌肉记忆。


📌 目录

  1. 滑动窗口核心思想:食堂打饭的比喻
  2. [题目一:长度最小的子数组(LeetCode 209)](#题目一:长度最小的子数组(LeetCode 209))
  3. [题目二:水果成篮(LeetCode 904)](#题目二:水果成篮(LeetCode 904))
  4. [题目三:找到字符串中所有字母异位词(LeetCode 438)](#题目三:找到字符串中所有字母异位词(LeetCode 438))
  5. [题目四:字符串的排列(LeetCode 567)](#题目四:字符串的排列(LeetCode 567))
  6. [题目五:最小覆盖子串(LeetCode 76)](#题目五:最小覆盖子串(LeetCode 76))
  7. 滑动窗口全题型总结
  8. AI知识扩展:滑动窗口与大模型上下文管理
  9. 留给你的思考题

一、滑动窗口核心思想:食堂打饭的比喻

想象你在学校食堂排队打饭。窗口([left, right])里的菜必须满足某种条件------比如"荤素搭配"(无重复字符)或"总价不超过预算"(和 ≥ target)。你的目标是找到满足条件的最长/最短窗口。

滑动窗口的本质

  • right 指针不断向右扩展("打更多的菜")。
  • 当窗口不满足条件时,left 指针向右收缩("扔掉一些菜")。
  • 在这个过程中,你记录下所有满足条件的窗口,取最优值。

三类滑动窗口

类型 代表题目 核心特征
不定长求最小 209. 长度最小的子数组 满足条件时收缩左边界
不定长求最大 904. 水果成篮 不满足条件时收缩左边界
固定长度 438. 异位词 / 567. 排列 窗口大小固定,滑动比较
带哈希表验证 76. 最小覆盖子串 需要计数验证窗口是否满足复杂条件

二、题目一:长度最小的子数组(LeetCode 209)

题目描述

给定一个含有 n 个正整数的数组和一个正整数 target,找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。如果不存在,返回 0

示例

复制代码
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是长度最小的满足条件的子数组。

从熟悉到陌生:自助餐的比喻

你在吃自助餐,目标是吃到至少 7 分饱(总和 ≥ target) ,但你想用最少的食物量达到这个目标。

  • 你不断往盘子里夹菜(right 右移),直到感觉吃饱了(和 ≥ target)。
  • 然后开始往外扔菜(left 左移),直到刚好吃饱为止------此时记录窗口长度。
  • 继续夹新菜,重复这个过程。

代码实现

python 复制代码
def minSubArrayLen(target, nums):
    left = 0
    total = 0
    min_len = float('inf')
    
    for right in range(len(nums)):
        total += nums[right]          # 右指针扩张
        while total >= target:        # 满足条件,尝试收缩
            min_len = min(min_len, right - left + 1)
            total -= nums[left]
            left += 1
    
    return min_len if min_len != float('inf') else 0

核心要点

要素 说明
窗口条件 窗口和 ≥ target
收缩时机 满足条件时立即收缩(求最小)
更新答案 每次满足条件时更新 min_len
复杂度 O(n),每个元素进出窗口各一次

三、题目二:水果成篮(LeetCode 904)

题目描述

你正在探访一个农场,农场有一排果树(用整数数组 fruits 表示,fruits[i] 是第 i 棵树上的水果种类)。你有两个篮子,每个篮子只能装一种水果。求最多能采摘多少棵树的果实(必须连续采摘,且最多两种类型)。

示例

复制代码
输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树,共两种水果(2和3)。

从熟悉到陌生:水果摊的比喻

你有一个只能装两种水果的篮子(最多两种类型)。你沿着水果摊走,想尽可能多地往篮子里装水果,但一旦出现第三种水果,你就必须从左边扔掉一些水果,直到篮子里只剩两种类型。

代码实现

python 复制代码
def totalFruit(fruits):
    left = 0
    max_len = 0
    count = {}                        # 记录窗口内每种水果的数量
    
    for right, fruit in enumerate(fruits):
        count[fruit] = count.get(fruit, 0) + 1
        
        while len(count) > 2:         # 超过两种水果,收缩窗口
            count[fruits[left]] -= 1
            if count[fruits[left]] == 0:
                del count[fruits[left]]
            left += 1
        
        max_len = max(max_len, right - left + 1)
    
    return max_len

核心要点

要素 说明
窗口条件 窗口中水果种类 ≤ 2
收缩时机 种类 > 2 时收缩(求最大)
更新答案 每次收缩后更新 max_len
复杂度 O(n),哈希表维护种类计数

四、题目三:找到字符串中所有字母异位词(LeetCode 438)

题目描述

给定两个字符串 sp,找到 s 中所有 p 的异位词子串,返回这些子串的起始索引。

示例

复制代码
输入:s = "cbaebabacd", p = "abc"
输出:[0, 6]
解释:索引0的子串 "cba" 是 "abc" 的异位词,索引6的子串 "bac" 也是。

从熟悉到陌生:固定长度的比喻

你有一个固定大小的模板(p 的长度),在长字符串 s 上滑动,每次检查窗口内的字符频率是否和模板完全一致。这是一个固定窗口 + 计数比较的问题。

代码实现

python 复制代码
def findAnagrams(s, p):
    from collections import Counter
    need = Counter(p)
    window = Counter()
    res = []
    left = 0
    
    for right in range(len(s)):
        window[s[right]] += 1
        
        if right - left + 1 > len(p):   # 窗口过大,左移
            window[s[left]] -= 1
            if window[s[left]] == 0:
                del window[s[left]]
            left += 1
        
        if window == need:
            res.append(left)
    
    return res

核心要点

要素 说明
窗口大小 固定为 len(p)
窗口条件 window == need
收缩时机 窗口大小超过 len(p) 时左移
复杂度 O(n),每次移动更新计数器

五、题目四:字符串的排列(LeetCode 567)

题目描述

给定两个字符串 s1s2,判断 s2 是否包含 s1 的排列(即 s1 的某个排列是 s2 的子串)。

示例

复制代码
输入:s1 = "ab", s2 = "eidbaooo"
输出:true
解释:s2 包含 "ba"("ab" 的排列之一)

与 438 的本质关系

这道题和 438 本质上完全一样------只是返回值从"所有起始索引"变成了"是否存在"。把 438 的代码稍作修改即可。

代码实现

python 复制代码
def checkInclusion(s1, s2):
    from collections import Counter
    need = Counter(s1)
    window = Counter()
    left = 0
    
    for right in range(len(s2)):
        window[s2[right]] += 1
        
        if right - left + 1 > len(s1):
            window[s2[left]] -= 1
            if window[s2[left]] == 0:
                del window[s2[left]]
            left += 1
        
        if window == need:
            return True
    
    return False

六、题目五:最小覆盖子串(LeetCode 76)

题目描述

给你一个字符串 s、一个字符串 t。返回 s 中涵盖 t 所有字符的最小子串 。如果不存在,返回空字符串 ""

示例

复制代码
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

从熟悉到陌生:拼图的比喻

你需要用 s 中的字符片段,拼出一个包含 t 中所有字符(且数量足够)的完整图案。你要找到最短的这样的片段。

这是滑动窗口中最复杂的一道,需要:

  1. 哈希表记录 t 中每个字符的需求量。
  2. 维护 valid 变量记录已满足的字符种类数。
  3. valid == len(need) 时,尝试收缩左边界。

代码实现

python 复制代码
def minWindow(s, t):
    from collections import defaultdict
    need = defaultdict(int)
    for ch in t:
        need[ch] += 1
    
    window = defaultdict(int)
    left = 0
    valid = 0
    start = 0
    min_len = float('inf')
    
    for right, ch in enumerate(s):
        # 右移窗口
        if ch in need:
            window[ch] += 1
            if window[ch] == need[ch]:
                valid += 1
        
        # 当所有字符都满足时,尝试收缩左边界
        while valid == len(need):
            if right - left + 1 < min_len:
                min_len = right - left + 1
                start = left
            
            d = s[left]
            left += 1
            if d in need:
                if window[d] == need[d]:
                    valid -= 1
                window[d] -= 1
    
    return "" if min_len == float('inf') else s[start:start+min_len]

核心要点

要素 说明
窗口条件 窗口内包含 t 的所有字符(数量足够)
验证机制 valid 记录已满足的字符种类数
收缩时机 满足条件时收缩(求最小)
复杂度 O(n),哈希表维护字符计数
难点 同时维护 valid 和字符计数

七、滑动窗口全题型总结

题目 窗口类型 条件 收缩时机 更新答案
209. 长度最小子数组 不定长 和 ≥ target 满足时收缩 每次收缩后
904. 水果成篮 不定长 种类 ≤ 2 不满足时收缩 每次收缩后
438. 异位词 固定长度 window == need 窗口超长时左移 每次匹配
567. 排列 固定长度 window == need 窗口超长时左移 发现即返回
76. 最小覆盖 不定长 valid == len(need) 满足时收缩 每次收缩后

核心模式模板

python 复制代码
left = 0
# 初始化数据结构

for right in range(len(s)):
    # 1. 右移窗口:加入 s[right]
    # 2. 更新窗口状态
    # 3. 判断是否需要收缩左边界
    while condition:                 # 不满足/满足条件
        # 更新答案
        # 左移窗口:移除 s[left]
        left += 1

八、AI知识扩展:滑动窗口与大模型上下文管理

8.1 上下文窗口:大模型的"滑动窗口"

在 Transformer 大模型中,上下文窗口(Context Window) 是决定模型"记忆长度"的核心参数。模型只能"看到"固定长度内的历史 token,这与滑动窗口的思想高度一致:

滑动窗口(算法题) 大模型上下文窗口
right 指针不断扩张 新 token 不断加入生成序列
left 指针收缩(丢弃旧元素) 窗口满时丢弃最老的 token
窗口内维护"合法条件" 窗口内维护"注意力范围"
最小/最大窗口长度 上下文窗口固定长度

8.2 RAG 中的 Top-K 检索

在 RAG 系统中,我们通常检索 Top‑K 个相关文档片段,然后拼接到 Prompt 中。这个 Top‑K 本质上也是一个固定长度的滑动窗口------它从向量数据库中"截取"最相关的 K 个片段,形成模型的"即时上下文"。

8.3 从"滑动窗口"到"注意力稀疏化"

当上下文窗口极大时(如 128K 或 1M),模型需要处理 O(n²) 的注意力计算。为了解决这个问题,研究人员开发了 滑动窗口注意力(Sliding Window Attention)------每个 token 只关注其附近的 W 个 token,而不是全部历史。

这和算法中的滑动窗口思想如出一辙:用局部信息替代全局信息,从而将时间复杂度从 O(n²) 降为 O(n×W)


九、留给你的思考题

问题 :如果 s 的长度是 10⁶,t 的长度是 100,minWindow 的时间复杂度是 O(n),空间复杂度 O(Σ)。你能解释为什么它比暴力枚举所有子串(O(n³))快这么多吗?

提示:暴力枚举所有子串需要 O(n²) 个子串,每个子串检查需要 O(n) 时间。而滑动窗口只让左右指针各遍历一次,且每次更新都是 O(1) 的哈希表操作。

连接到大模型 :在大模型的注意力机制中,为什么我们要用"滑动窗口注意力"来替代全局注意力?------因为序列越长,全局注意力的计算量增长越快(O(n²)),而滑动窗口注意力将每个 token 的关注范围限制在局部,将复杂度降为 O(n×W)。这和"子串枚举"与"滑动窗口"的关系惊人地相似------剪枝掉的不是答案,而是那些注定不是最优的搜索空间


相关推荐
Ivanqhz1 小时前
NFM(神经因子分解机)
开发语言·javascript·人工智能·算法·蓝桥杯
深圳市快瞳科技有限公司1 小时前
从人工录入到秒级识别:保单OCR识别厂家选型指南
人工智能·算法·计算机视觉·ocr
小欣加油1 小时前
leetcode1331 数组序号转换
数据结构·c++·算法·leetcode·职场和发展
学究天人1 小时前
数学公理体系大全:第五章 序数与基数理论:超限算术与集合的大小
人工智能·线性代数·算法·机器学习·数学建模·原型模式
阿米亚波1 小时前
【C++ STL】std::deque
数据结构·c++·笔记·算法·stl
金色旭光2 小时前
YOLO26 目标检测原理
算法·yolo
梦回江东2 小时前
递归的编译优化(2)
算法
学究天人2 小时前
数学公理体系大全:第六章 选择公理的等价形式及证明
人工智能·线性代数·算法·机器学习·数学建模·概率论·原型模式
:-)2 小时前
基础算法-插入排序
数据结构·算法·排序算法