LeetCode 滑动窗口问题 - 核心限制条件总结 (基于灵茶山艾府分类 - 详尽版)

主题:LeetCode 滑动窗口问题 - 核心限制条件总结 (基于灵茶山艾府分类 - 详尽版)

核心思想:

滑动窗口通过维护一个动态的(或固定大小的)子序列(窗口)来解决问题。关键在于定义窗口的"有效性"(即限制条件)以及如何高效地移动窗口(扩展右边界,收缩左边界,或整体平移)。

一、定长滑动窗口

窗口大小固定为 k 或某个题目给定的常数。主要任务是在这个固定大小的窗口上进行计算或检查。

  • §1.1 基础

    • 1456. 定长子串中元音的最大数目

      • 核心限制: 子串长度必须为 k
      • 滑动窗口应用: 维护一个长度为 k 的窗口,在字符串上从左到右滑动。进入窗口的字符检查是否元音并更新计数,移出窗口的字符若为元音则减少计数。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 计算每个窗口内元音字母的数量,并找出所有窗口中的最大元音数。
    • 643. 子数组最大平均数 I

      • 核心限制: 子数组长度必须为 k
      • 滑动窗口应用: 维护一个长度为 k 的窗口,计算窗口内元素的和。当窗口滑动时,减去移出窗口的元素,加上进入窗口的元素,以 O(1) 更新和。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 计算每个窗口内元素的平均值 (和/k),并找出所有窗口中的最大平均值。
    • 1343. 大小为 K 且平均值大于等于阈值的子数组数目

      • 核心限制: 子数组长度必须为 k。子数组的平均值需 >= threshold
      • 滑动窗口应用: 维护一个长度为 k 的窗口,计算窗口内元素的和。
      • 窗口限制: 窗口大小严格等于 k
      • 附加条件检查: 对于每个窗口,检查其 sum >= threshold * k (避免浮点数运算)。
      • 任务: 统计满足上述大小和平均值条件的窗口数量。
    • 2090. 半径为 k 的子数组平均值

      • 核心限制: 对于每个中心点 i,子数组包含 nums[i-k]nums[i+k],即长度为 2k+1
      • 滑动窗口应用: 维护一个长度为 2k+1 的窗口,计算窗口和。
      • 窗口限制: 窗口大小严格等于 2k+1。如果数组索引不足以形成这样长度的窗口(例如数组边缘元素作为中心时),则该中心的平均值为 -1。
      • 任务: 为每个数组元素计算其作为中心的子数组平均值(整数除法)。
    • 2379. 得到 K 个黑块的最少涂色次数

      • 核心限制: 需要得到一个长度为 k 的连续黑块。
      • 滑动窗口应用: 维护一个长度为 k 的窗口,在表示颜色的字符串上滑动。统计窗口内 'W' (白块) 的数量。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 计算每个窗口内白块 ('W') 的数量,这个数量即为将该窗口变为全黑块所需的涂色次数。找出所有窗口中的最小涂色次数。
    • 2841. 几乎唯一子数组的最大和

      • 核心限制: 子数组长度必须为 k。子数组中不同元素的数目必须至少为 m
      • 滑动窗口应用: 维护一个长度为 k 的窗口,并用哈希表记录窗口内元素的频率和不同元素种类数,同时维护窗口内元素的和。
      • 窗口限制: 窗口大小严格等于 k
      • 附加条件检查: 窗口内不同元素数目 >= m
      • 任务: 在所有满足上述条件的窗口中,找出元素和的最大值。若没有满足条件的窗口,返回0。
    • 2461. 长度为 K 子数组中的最大和

      • 核心限制: 子数组长度必须为 k。子数组中所有元素必须是唯一的。
      • 滑动窗口应用: 维护一个长度为 k 的窗口,并用哈希表/集合记录窗口内元素的出现情况以检查唯一性,同时维护窗口内元素的和。
      • 窗口限制: 窗口大小严格等于 k
      • 附加条件检查: 窗口内所有元素唯一(哈希表中所有值的频率为1,或集合大小为k)。
      • 任务: 在所有满足上述条件的窗口中,找出元素和的最大值。若没有满足条件的窗口,返回0。
    • 1423. 可获得的最大点数

      • 核心限制 (间接): 总共取 k 张卡牌,可以从头部取 i 张,尾部取 k-i 张。
      • 滑动窗口应用: 转换为寻找一个长度为 n-k (其中 n 是数组总长度) 的中间子数组,使其和最小。总和减去这个最小和即为答案。滑动窗口用于找到这个和最小的长度为 n-k 的子数组。
      • 窗口限制 (转换后): 窗口大小严格等于 n-k
      • 任务 (转换后): 找到和最小的长度为 n-k 的子数组。最终任务是 totalSum - minSubarraySum。如果 n-k <= 0,则取所有元素的和。
    • 1052. 爱生气的书店老板

      • 核心限制: 老板可以连续 minutes 分钟不生气。
      • 滑动窗口应用: 首先计算老板在不使用技巧时就能获得的顾客数(grumpy[i] == 0 的部分)。然后,滑动一个长度为 minutes 的窗口,计算在这个窗口内应用"不生气"技巧能额外挽回的顾客数量(即窗口内 customers[i]grumpy[i]==1 时的和)。
      • 窗口限制: 窗口大小严格等于 minutes
      • 任务: 找出额外挽回顾客数的最大值,并将其与老板原本就不生气时段的顾客数相加,得到总满意顾客数。
    • 1652. 拆炸弹

      • 核心限制: 解密后的数字是原数字后面/前面 k 个数字的和 (根据 k 的正负和0)。数组是环形的。
      • 滑动窗口应用: (O(n)做法) 构造一个两倍或三倍的原数组来处理环形。
        • k > 0: 窗口大小为 k,求 code[i] 右边 k 个元素的和。
        • k < 0: 窗口大小为 abs(k),求 code[i] 左边 abs(k) 个元素的和。
        • k == 0: 结果为0。
          初始化第一个窗口的和,然后滑动窗口,O(1)更新和。
      • 窗口限制: 窗口大小严格等于 abs(k) (若k!=0)。
      • 任务: 为原数组的每个元素计算其解密后的值,存入新数组。
    • 1176. 健身计划评估(会员题)

      • 核心限制: 评估连续 k 天的饮食,卡路里总和 T
      • 滑动窗口应用: 维护一个长度为 k 的窗口,计算窗口内元素的和。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 对每个窗口,计算其元素和 T。若 T < lower 减1分,若 T > upper 加1分。统计总得分。
    • 1100. 长度为 K 的无重复字符子串(会员题)

      • 核心限制: 子串长度为 k,且子串内字符唯一。
      • 滑动窗口应用: 维护一个长度为 k 的窗口,并用哈希表或频率数组检查窗口内字符的唯一性(所有字符频率为1)。
      • 窗口限制: 窗口大小严格等于 k
      • 附加条件检查: 窗口内字符唯一。
      • 任务: 统计满足条件的窗口数量。
    • 1852. 每个子数组的数字种类数(会员题)

      • 核心限制: 对每个长度为 k 的子数组进行操作。
      • 滑动窗口应用: 维护一个长度为 k 的窗口,并用哈希表记录窗口内元素的频率。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 对每个窗口,计算其内部不同数字的种类数(哈希表中键的数量),形成结果数组。
    • 1151. 最少交换次数来组合所有的 1(会员题)

      • 核心限制: 目标是将所有 1 组合到一个连续子数组中。
      • 滑动窗口应用: 首先统计数组中 1 的总数 count_ones。这个值就是目标连续子数组的长度。然后滑动一个长度为 count_ones 的窗口。
      • 窗口限制: 窗口大小严格等于 count_ones
      • 任务: 计算每个窗口内 0 的数量。这个数量即为将该窗口填满 1 所需的最小交换次数(因为这些 0 需要和窗口外的 1 交换)。找出所有窗口中这个 0 的数量的最小值。
    • 2107. 分享 K 个糖果后独特口味的数量(会员题)

      • 核心限制: 考虑所有连续的 k 个糖果被分享后,剩余糖果的独特口味。这似乎是指移除长度为k的子数组后,剩余两段的独特口味。或者更可能是:从 n 个糖果中拿走 k 个,求剩下的 n-k 个糖果的独特口味有多少种组合。如果题目意为:考虑一个固定长度为 n-k 的子数组(代表一次保留的糖果),求其中独特口味的数量。
      • 滑动窗口应用 (假设为保留 n-k 个的场景): 维护一个长度为 n-k 的窗口。用哈希表统计窗口内糖果口味的频率。
      • 窗口限制: 窗口大小严格等于 n-k
      • 任务: 对每个窗口,计算其内部独特口味的数量(哈希表中键的数量)。
  • §1.2 进阶(选做)

    • 3439. 重新安排会议得到最多空余时间 I (题目较新,具体细节需查阅)

      • 核心限制 (推测): 可能给定一系列已安排的会议 [[start, end], ...] 和新会议的时长 duration
      • 滑动窗口应用 (推测): 将所有会议按开始时间排序。空余时间在会议之间或首尾。可以计算出所有空闲间隙。然后看这些间隙是否能容纳 duration。如果问题是求能插入的最长空闲,可能不是经典滑窗。如果是 "最多空余时间" 可能是指找到一个时间点开始,长度为 X 的时段,使得这个时段内未被占用的时间最长。
      • 窗口限制 (推测): 窗口可能代表一个固定长度的时间段 X,或者是一个可变的空闲间隙。
      • 任务 (推测): 最大化某个指标,如未被占用时间或可安排的新会议数。
    • 2134. 最少交换次数来组合所有的 1 II

      • 核心限制: 数组是环形的。目标是将所有 1 组合到一个连续子数组中。
      • 滑动窗口应用: 类似1151。统计 1 的总数 count_ones。将原数组复制一份接在后面以处理环形。滑动一个长度为 count_ones 的窗口。
      • 窗口限制: 窗口大小严格等于 count_ones
      • 任务: 计算每个窗口内 0 的数量,求最小值。
    • 1297. 子串的最大出现次数

      • 核心限制: 子串长度在 minSizemaxSize 之间。子串中的不同字符数不能超过 maxLetters
      • 滑动窗口应用: 固定窗口大小为 minSize 进行遍历。因为如果一个更长的合法子串出现 x 次,它包含的每个长度为 minSize 的合法子串至少也出现 x 次。我们只关心出现次数,所以考虑最短的合法长度 minSize 就足够了。维护一个长度为 minSize 的窗口,并用哈希表记录窗口内字符频率和不同字符数。
      • 窗口限制 (针对 minSize): 窗口大小固定为 minSize。附加限制:窗口内不同字符数 <= maxLetters
      • 任务: 统计所有满足上述条件的、长度为 minSize 的子串的出现次数,并找出最大出现次数。
    • 2653. 滑动子数组的美丽值

      • 核心限制: 子数组长度为 k。美丽值定义为子数组中第 x 小的数,但仅当这个数是负数时才计数,否则美丽值为0。如果负数个数少于 x,美丽值为0。
      • 滑动窗口应用: 维护一个长度为 k 的窗口。需要在窗口滑动时高效地找到第 x 小的负数。通常使用频率数组(如果数值范围有限,如-50到0)或两个平衡树(一个存负数,一个存非负数)或一个支持高效查找第k小的有序数据结构。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 对每个窗口,计算其美丽值,形成结果数组。
    • 1888. 使二进制字符串字符交替的最少反转次数

      • 核心限制: 目标是使字符串变为 "0101..." 或 "1010..."。允许将原字符串看作环形(首尾相接)。
      • 滑动窗口应用: 将字符串 s 复制一份得到 s_double = s + s。然后在这个长度为 2n 的新串上滑动一个长度为 n (原字符串长度) 的窗口。
      • 窗口限制: 窗口大小严格等于 n
      • 任务: 对每个窗口,分别计算其与目标模式 "0101..." (长度n, 根据窗口起始位置的奇偶性决定) 和 "1010..." (长度n) 的汉明距离(不同字符数)。取这两种模式下较小的汉明距离。最终结果是所有窗口得到的最小汉明距离。
    • 567. 字符串的排列

      • 核心限制: 判断 s2 是否包含 s1 的排列(即 s1 的一个字母异位词)。
      • 滑动窗口应用: 维护一个长度为 len(s1) 的窗口在 s2 上滑动。使用两个频率数组或哈希表,一个记录 s1 的字符频率 (need),一个记录当前窗口内字符频率 (window_freq)。
      • 窗口限制: 窗口大小严格等于 len(s1)
      • 附加条件检查: window_freqneed 完全一致。
      • 任务: 如果存在任何一个窗口满足上述条件,则返回 true,否则返回 false
    • 438. 找到字符串中所有字母异位词

      • 核心限制: 找到 s 中所有是 p 的字母异位词的子串的起始索引。
      • 滑动窗口应用: 维护一个长度为 len(p) 的窗口在 s 上滑动。使用频率数组或哈希表比较字符频率。
      • 窗口限制: 窗口大小严格等于 len(p)
      • 附加条件检查: 当前窗口内字符的频率分布与 p 的字符频率分布完全一致。
      • 任务: 收集所有满足条件的窗口的起始索引。
    • 30. 串联所有单词的子串

      • 核心限制: 子串必须由 words 数组中所有单词串联而成,每个单词恰好出现一次,顺序不限,且单词之间没有其他字符。
      • 滑动窗口应用:wordLen = len(words)numWords = len(words)。窗口总长度固定为 totalLen = wordLen * numWords。由于单词长度固定,需要从 0wordLen - 1wordLen 个不同的起始偏移量开始滑动。对每个起始偏移量,以 wordLen 为步长检查窗口。在主窗口内部,再将窗口划分为 numWords 个小段,每段长度为 wordLen
      • 窗口限制: 主窗口大小严格等于 totalLen
      • 附加条件检查: 主窗口可以被精确划分为 numWords 个长度为 wordLen 的小段,这些小段形成的集合(考虑频率)与 words 数组形成的集合(考虑频率)相同。通常使用哈希表来匹配这些小段。
      • 任务: 收集所有满足条件的窗口的起始索引。
    • 2156. 查找给定哈希值的子串

      • 核心限制: 子串长度为 k。其哈希值(按特定公式使用 powermodulo 计算)等于 targetHash
      • 滑动窗口应用: 维护一个长度为 k 的窗口,并使用滚动哈希算法高效计算窗口内子串的哈希值。
      • 窗口限制: 窗口大小严格等于 k
      • 附加条件检查: 窗口内子串的计算哈希值等于 targetHash。由于哈希冲突,找到匹配哈希后最好再做一次字符串的直接比较(虽然题目可能假设哈希函数足够好)。
      • 任务: 找到第一个满足条件的子串。
    • 2953. 统计完全子字符串

      • 核心限制: "完全子字符串"定义为:其中每个字符都恰好出现 k 次。
      • 滑动窗口应用: 这是一个不定长窗口问题,但可以约束长度。可以枚举子串中不同字符的种类数 c (从 1 到 26)。如果一个子串是完全的且包含 c 种不同字符,那么它的长度必然是 c * k。对于每个可能的长度 L = c * k,使用一个定长为 L 的滑动窗口。
      • 窗口限制 (对于固定的 L = c*k): 窗口大小固定为 L
      • 附加条件检查: 窗口内恰好有 c 种不同字符,并且每种字符都出现了 k 次。
      • 任务: 统计所有满足条件的窗口数量。 (注:更自然的解法可能是不定长窗口,但题目在"定长"分类下,所以提供此思路)。
    • 1016. 子串能表示从 1 到 N 数字的二进制串 (做到 O(∣s∣))

      • 核心限制: 字符串 s 的子串中需要包含从 1 到 N 所有整数的二进制表示。
      • 滑动窗口应用: 遍历 s 中所有长度在 1log2(N)+1(N的二进制表示的最大可能长度)之间的子串。将这些子串视为二进制数,转换为十进制存入一个集合。
      • 窗口限制: 窗口长度从 1 迭代到 len(bin(N))。对于每个长度,滑动窗口遍历 s
      • 任务: 检查集合中是否包含了从 1N 的所有数字。O(|s|) 的做法通常是指对每个可能的子串长度(有限个,因为N不大时logN也小),滑动窗口并计算/哈希子串,总复杂度与|s|乘以logN的某个小因子相关。
    • 683. K 个关闭的灯泡(会员题)做到 O(n)

      • 核心限制: 找到一对亮着的灯泡 bulbs[i]bulbs[j],它们之间恰好有 k 个熄灭的灯泡,并且 bulbs[i]bulbs[j] 是距离最近的这样一对。
      • 滑动窗口应用 (O(n) days array): 题目给的是灯泡亮起的天数。可以创建一个 days 数组,days[i] 表示第 i+1 号灯泡亮起的天数。然后问题转化为在 days 数组中找一个长度为 k+2 的窗口 days[x...x+k+1],使得 days[x]days[x+k+1] 都小于窗口中间 k 个元素 days[x+1...x+k]days 值。
      • 窗口限制: 窗口长度为 k+2
      • 任务: 检查窗口端点的 days 值是否都小于中间所有元素的 days 值。
    • 2067. 等计数子串的数量(会员题)

      • 核心限制: 子串中每个出现过的字母其出现次数都相同(例如,"aaabbbccc" 中 a,b,c 都出现3次)。
      • 滑动窗口应用: 可以枚举每种字符的出现次数 count (从1到 n/num_distinct_chars) 和不同字符的种类数 distinct_chars (从1到26)。则窗口长度为 L = count * distinct_chars。使用定长为 L 的滑动窗口。
      • 窗口限制: 窗口大小为 L
      • 附加条件检查: 窗口内恰好有 distinct_chars 种字符,且每种字符都出现了 count 次。
      • 任务: 统计满足条件的窗口数量。
    • 2524. 子数组的最大频率分数(会员题)

      • 核心限制: 子数组长度为 k。频率分数定义为 sum((frequency of char)^exponent) % modulo
      • 滑动窗口应用: 维护一个长度为 k 的窗口,用哈希表记录字符频率。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 对每个窗口,计算其频率分数,找出最大值。
  • §1.3 其他(选做)

    • 2269. 找到一个数字的 K 美丽值

      • 核心限制: 将数字 num 转为字符串 s。子串长度为 k
      • 滑动窗口应用: 在字符串 s 上滑动一个长度为 k 的窗口。
      • 窗口限制: 窗口大小严格等于 k
      • 附加条件检查: 窗口代表的子串转换回数字后(记为 sub_val),sub_val 不能为0,且原始数字 num 必须能被 sub_val 整除。
      • 任务: 统计满足条件的窗口数量。
    • 1984. 学生分数的最小差值

      • 核心限制: 选出 k 个学生,使得他们的最高分与最低分之差最小。
      • 滑动窗口应用: 首先对学生分数数组 nums 进行排序。然后滑动一个长度为 k 的窗口。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 对于每个窗口,差值为 window_max - window_min。由于已排序,这等于 nums[right_idx] - nums[left_idx]。找出所有窗口中的最小差值。
    • 1461. 检查一个字符串是否包含所有长度为 K 的二进制子串

      • 核心限制: 需要检查 2^k 个所有可能的长度为 k 的二进制串。
      • 滑动窗口应用: 在字符串 s 上滑动一个长度为 k 的窗口。
      • 窗口限制: 窗口大小严格等于 k
      • 任务: 将每个窗口代表的二进制子串(可以直接是字符串,或转换为整数)存入一个集合中。最后检查集合的大小是否等于 2^k
    • 220. 存在重复元素 III

      • 核心限制: 存在两个不同下标 ij,使得 abs(nums[i] - nums[j]) <= valueDiff (原题为t) 且 abs(i - j) <= indexDiff (原题为k)。
      • 滑动窗口应用: 维护一个大小至多为 indexDiff 的窗口(窗口内存储的是元素值)。当右指针 j 对应的元素 nums[j] 加入时,检查窗口内是否有元素 x 满足 abs(nums[j] - x) <= valueDiff。如果窗口大小超过 indexDiff (即 j - i > indexDiff,其中 i 是窗口最左元素的原始索引),则从窗口中移除最左边的元素 nums[i]
      • 窗口限制(逻辑上的): 窗口代表索引差在 indexDiff 内的一组元素。
      • 维护方式: 通常使用有序数据结构(如 std::set 在C++中,或 TreeSet 在Java中)来高效查找满足 valueDiff 条件的元素。对于新元素 v = nums[j],在集合中查找范围 [v - valueDiff, v + valueDiff] 内的元素。
      • 任务: 判断是否存在这样的 ij

二、不定长滑动窗口

窗口大小不固定,根据窗口内元素满足的条件动态调整。

  • §2.1 求最长/最大 (一般题目都有「至多」的要求,不满足则收缩左边界)

    • §2.1.1 基础

      • 3. 无重复字符的最长子串:

        • 核心限制: 子串内字符唯一。
        • 滑动窗口应用: 右指针扩展窗口,将新字符加入哈希集合/频率数组。若新字符导致重复(已存在于集合或频率>1),则左指针收缩窗口,从集合/频率数组中移除对应字符,直到窗口再次满足唯一性。
        • 窗口限制: 窗口内所有字符必须是唯一的(哈希集合中无重复,或频率数组中所有相关字符频率为1)。
        • 任务: 在每次窗口有效时,更新记录到的最大窗口长度。
      • 3090. 每个字符最多出现两次的最长子字符串:

        • 核心限制: 子字符串中每个字符的出现次数不能超过两次。
        • 滑动窗口应用: 右指针扩展,用哈希表/频率数组计数。若某字符进入窗口后其频率超过两次,左指针收缩窗口并更新频率,直到该字符频率及所有字符频率符合要求。
        • 窗口限制: 窗口内每个字符的出现次数 <= 2
        • 任务: 在每次窗口有效时,更新记录到的最大窗口长度。
      • 1493. 删掉一个元素以后全为 1 的最长子数组:

        • 核心限制: 最多允许删除一个元素 (即子数组中最多包含一个0)。
        • 滑动窗口应用: 右指针扩展,计数窗口内0的个数。若0的个数超过1,左指针收缩窗口并更新0的计数,直到0的个数符合要求。
        • 窗口限制: 窗口内 0 的数量 <= 1
        • 任务: 记录满足条件的窗口的最大长度。注意,如果窗口内只有一个0,长度是 right-left;如果全是1,长度是 right-left+1。题目要求删除一个元素后全为1,所以如果窗口内0的个数是0或1,其有效长度是 right-left (因为删除的那个0不算在最终长度里,或者没有0可删)。最终答案是 max_len (若原始数组全1,需要特殊处理,或者理解为窗口长度 right-left+1,最终结果减1)。或者直接是 max(right - left) (这里的left, right是0-indexed)。
      • 1208. 尽可能使字符串相等:

        • 核心限制:s 的子串变为 t 对应子串的总代价 (ASCII差值绝对值之和) 不超过 maxCost
        • 滑动窗口应用: 右指针扩展,累加新加入字符对的转换代价。若总代价超过 maxCost,左指针收缩窗口并从总代价中减去移出字符对的代价,直到总代价符合要求。
        • 窗口限制: 窗口内对应字符转换的 sum(abs(s[i] - t[i])) <= maxCost
        • 任务: 在每次窗口有效时,更新记录到的最大窗口长度。
      • 904. 水果成篮:

        • 核心限制: 最多只能包含两种不同类型的水果。
        • 滑动窗口应用: 右指针扩展,用哈希表记录水果种类和其在窗口内的数量/频率。若不同水果种类超过2,左指针收缩窗口并更新水果频率,直到种类数符合要求(某水果频率降为0时从种类中移除)。
        • 窗口限制: 窗口内不同水果的种类数 <= 2
        • 任务: 在每次窗口有效时,更新记录到的最大窗口长度(即最多水果数量)。
      • 1695. 删除子数组的最大得分:

        • 核心限制: 子数组中所有元素必须是唯一的。得分为子数组元素之和。
        • 滑动窗口应用: 右指针扩展,将新元素加入哈希集合并累加到当前窗口和。若新元素导致重复(已存在于集合),则左指针收缩窗口,从集合中移除对应元素并从当前和中减去该元素,直到窗口再次满足唯一性。
        • 窗口限制: 窗口内所有元素唯一。
        • 任务: 在每次窗口有效时,更新记录到的最大窗口元素和。
      • 2958. 最多 K 个重复元素的最长子数组:

        • 核心限制: 子数组中任何一个元素的出现频率都不能超过 K 次。
        • 滑动窗口应用: 右指针扩展,用哈希表/频率数组计数。若某元素进入窗口后其频率超过 K,左指针收缩窗口并更新频率,直到该元素频率及所有元素频率符合要求。
        • 窗口限制: 窗口内任何一个元素的出现频率 <= K
        • 任务: 在每次窗口有效时,更新记录到的最大窗口长度。
      • 2024. 考试的最大困扰度:

        • 核心限制: 最多可以将 k 个 'T' 变成 'F',或将 k 个 'F' 变成 'T'。求最长连续 'T' 或 'F'。
        • 滑动窗口应用: 此问题需要分别计算两种情况,然后取最大值:
          1. 目标为全'T':右指针扩展,计数窗口内'F'的数量。若'F'的数量超过 k,左指针收缩窗口并更新'F'的计数。
          2. 目标为全'F':右指针扩展,计数窗口内'T'的数量。若'T'的数量超过 k,左指针收缩窗口并更新'T'的计数。
        • 窗口限制 (针对目标'T'): 窗口内 'F' 的数量 <= k
        • 窗口限制 (针对目标'F'): 窗口内 'T' 的数量 <= k
        • 任务: 对每种情况,在每次窗口有效时,更新记录到的最大窗口长度。最终答案是两种情况中得到的最大长度中的较大者。
      • 1004. 最大连续 1 的个数 III:

        • 核心限制: 最多可以将 K 个 0 翻转成 1。求包含翻转后最长连续1的子数组长度。
        • 滑动窗口应用: 右指针扩展,计数窗口内0的个数。若0的个数超过 K,左指针收缩窗口并更新0的计数,直到0的个数符合要求。
        • 窗口限制: 窗口内 0 的数量 <= K
        • 任务: 在每次窗口有效时,更新记录到的最大窗口长度。
      • 1658. 将 x 减到 0 的最小操作数:

        • 核心限制 (转化后): 从数组两端移除元素,使得移除元素的和等于 x。求移除元素的最少数量。
        • 滑动窗口应用 (转化后): 问题等价于找到一个最长的中间子数组,其和等于 targetSum = totalArraySum - x。使用不定长滑动窗口寻找和为 targetSum 的最长子数组。右指针扩展累加和,若当前和超过 targetSum 或等于 targetSum 时需要调整左指针。
        • 窗口限制 (转化后): 窗口内元素和等于 targetSum
        • 任务 (转化后): 找出满足条件的窗口的最大长度 maxLen。如果找到了这样的 maxLen,则最终答案是 arrayLength - maxLen。若 targetSum < 0 或找不到,则无解(或为-1,具体看题目要求)。
    • §2.1.2 进阶(选做)

      • 2730. 找到最长的半重复子字符串 (非暴力做法):

        • 核心限制: "半重复子字符串"是指其中至多有一对相邻字符是相同的 (例如, "aabbc" 中的 "aa" 或 "bb" 是一对,"aaabbc" 中的 "aaa" 包含两对 "aa")。题目应指至多一个位置 s[i] == s[i+1]
        • 滑动窗口应用: 右指针扩展。记录窗口内 s[j] == s[j-1] 的位置(或计数这样的对)。如果这样的对超过1个,左指针收缩,直到对的数量 <=1
        • 窗口限制: 窗口内相邻且相同的字符对的数量 <= 1
        • 任务: 更新最大窗口长度。
      • 2779. 数组的最大美丽值:

        • 核心限制: 子序列中最大元素与最小元素之差不超过 2*k。美丽值是子序列的长度。
        • 滑动窗口应用: 首先对数组 nums 排序。然后使用双指针(看作滑动窗口的边界)。右指针 j 扩展,左指针 i 尝试向右移动。
        • 窗口限制: nums[j] - nums[i] <= 2*k。当此条件不满足时(即 nums[j] - nums[i] > 2*k),需要移动左指针 i
        • 任务: 在满足条件时,窗口长度 j-i+1 是一个可能的美丽值。记录最大值。
      • 1838. 最高频元素的频数:

        • 核心限制: 可以执行最多 k 次操作(每次元素加1),求操作后数组中某个元素可能的最大频数。
        • 滑动窗口应用: 先对数组排序。对于每个 nums[i] 作为目标值(操作后窗口内所有元素都变成 nums[i]),维护一个以 i 为右边界的窗口 [left, i]。计算将 nums[left...i-1] 都增加到 nums[i] 所需的总操作成本。
        • 窗口限制: (i - left) * nums[i] - sum(nums[left...i-1]) <= k (窗口内除了 nums[i] 外的元素变成nums[i]的总成本)。或者理解为将窗口 [left...i] 内所有元素变成 nums[i] 的成本 (i - left + 1) * nums[i] - sum(nums[left...i]) <= k。通常用前缀和优化 sum 的计算。当成本超 kleft 右移。
        • 任务: 更新最大窗口长度 i - left + 1
      • 2516. 每种字符至少取 K 个:

        • 核心限制: 找到一个子数组,其中每种 在原数组中出现过 的字符,都至少出现 K 次。求这样的子数组的最小长度。 (注意:这题是求最短,放在"求最长/最大"分类下可能有点误导,除非是间接求解)
        • 滑动窗口应用: 先统计原数组中所有不同字符及其总频率。然后滑动窗口,维护窗口内各字符频率。
        • 窗口限制: 窗口内,对于原数组中出现过的每一种字符 c,其在窗口内的频率 freq[c] 必须 >= K。同时,窗口内不同字符的种类数必须等于原数组中不同字符的种类数(确保没有遗漏任何一种字符)。
        • 任务: 找到满足条件的最小窗口长度。当窗口满足条件后,尝试收缩左边界。
      • 2831. 找出最长等值子数组:

        • 核心限制: 可以从数组中删除最多 k 个元素,得到一个最长的、所有元素都相等的子数组。
        • 滑动窗口应用: 对数组中每种不同的值 val 分别进行处理。对于固定的 val,目标是找到一个最长的子数组,其中包含尽可能多的 val,并且其他元素(需要被删除的元素)的数量不超过 k
        • 窗口限制 (针对特定值 val): 窗口内非 val 元素的数量 <= k
        • 任务: 对每种 val,找到满足上述条件的最长窗口,并记录窗口内 val 的数量。最终答案是所有 val 中得到的最大 val 数量。
      • 2271. 毯子覆盖的最多白色砖块数:

        • 核心限制: 毯子长度为 carpetLen。求毯子能覆盖的最多白色砖块数。
        • 滑动窗口应用: 将白色砖块的位置 floor[i]floor[i] 展开或只关注其起始点。更简单的是,只考虑白色砖块的起始点。将所有白色砖块的起始点排序。然后,对于每个砖块 i 作为毯子可能覆盖的第一个砖块,毯子的覆盖范围是 [start_i, start_i + carpetLen - 1]。统计这个范围内有多少砖块。这可以用一个变种的滑动窗口:右指针代表毯子右端,左指针代表毯子左端。或者,对于每个砖块 j,假设毯子的右端刚好覆盖到它,则毯子的左端是 pos_j - carpetLen + 1,然后统计这个范围内的砖块。
          一个更标准的滑窗:将所有白色砖块的坐标点(每个砖块是一个区间,可能需要处理成点)收集并排序。然后滑动一个长度为 carpetLen 的"逻辑窗口"在这些坐标点上。
          或者,对每个砖块 i 的起始点 s_i,二分查找有多少砖块的起始点 <= s_i + carpetLen - 1
          纯滑窗思路: 将所有白色砖块的起始点排序。右指针 r 遍历砖块,左指针 l 维护窗口的左边界。
        • 窗口限制: brick_pos[r] - brick_pos[l] + 1 <= carpetLen (如果砖块本身有长度,则为 brick_end_pos[r] - brick_start_pos[l] + 1 <= carpetLen,或者更精确地是 brick_start_pos[r]brick_start_pos[l] 的关系)。
          如果把砖块看作点(其起始位置),则当 brick_pos[r] - brick_pos[l] >= carpetLen 时,移动 l
        • 任务: 维护窗口内砖块数量,取最大值。
      • 2106. 摘水果:

        • 核心限制:startPos 出发,总移动步数不超过 k。求能摘到的最大水果数。可以向左或向右,或先一边再另一边。
        • 滑动窗口应用: 这不是典型的线性滑动窗口。可以枚举两种主要策略:
          1. 只往一个方向走。
          2. 先往一个方向走 x 步,再掉头往另一个方向走 y 步。约束是 x+y <= k (如果只考虑单程距离) 或 2x+y <= k (先左后右,左边往返) 或 x+2y <= k (先右后左,右边往返)。
            对每种策略,确定能到达的左右边界,然后累加该区间的水果。
            前缀和+枚举/滑窗变种: 计算水果数量的前缀和。枚举向左走的距离 left_dist (0 to k)。则剩余步数 rem_steps = k - 2*left_dist (因为向左走需要返回到 startPos),这些步数可以用来向右走。或者 rem_steps = k - left_dist,这些步数可以向右走,但总路径是 left_dist + right_dist (如果不需要返回)。
            遍历所有可能的分割点(最左或最右到达的水果),用前缀和快速计算区间和。
        • 窗口限制: 逻辑上的窗口由 startPos、向左走的距离、向右走的距离以及总步数 k 共同定义。
        • 任务: 最大化摘取的水果数量。
      • 2009. 使数组连续的最少操作数:

        • 核心限制: 目标是得到一个长度为 n (原数组长度) 的连续整数序列 (如 x, x+1, ..., x+n-1)。操作是将任何元素更改为任何整数。求最少操作数。
        • 滑动窗口应用: 首先对数组 nums 去重并排序。现在我们有一组独特的、有序的数字。我们想找到一个子序列,它们的值可以构成目标连续序列的一部分。滑动窗口 [i, j] 在这个去重排序后的数组上。
        • 窗口限制: unique_sorted_nums[j] - unique_sorted_nums[i] < n。 (因为如果它们构成长度为 m = j-i+1 的子序列,要使它们连续且长度为 n,它们本身的值域跨度不能超过 n-1)。
        • 任务: 对于每个满足条件的窗口 [i, j],它包含了 m = j-i+1 个独特的数字。这些数字可以作为目标长度为 n 的连续序列的一部分。我们保留这 m 个数字,另外 n-m 个数字需要通过操作得到。所以操作数是 n-m。目标是最大化 m,从而最小化 n-m
      • 1610. 可见点的最大数目:

        • 核心限制: 给定观察点 location,视野角度 angle,和一系列点 points。求最多能同时看到的点数。
        • 滑动窗口应用:
          1. 将所有点转换为相对于 location 的极坐标角度。与 location 重合的点特殊处理(总是可见)。
          2. 对所有角度进行排序。
          3. 由于视野是环形的(例如,从350度到10度是合法的),将排序后的角度数组复制一份,每个角度加上360度,然后拼接到原数组后面。得到一个长度为 2m 的角度数组。
          4. 在这个扩展的角度数组上使用滑动窗口。右指针 j 扩展,左指针 i 维护。
        • 窗口限制: extended_angles[j] - extended_angles[i] <= angle
        • 任务: 窗口内的点数 j-i+1 就是当前能看到的点数。记录最大值。加上与 location 重合的点数。
      • 2781. 最长合法子字符串的长度:

        • 核心限制: 子字符串中不能包含 forbidden 列表中的任何一个禁止子串。
        • 滑动窗口应用: 右指针 r 扩展。对于每个新的右端点 r,检查以 r 结尾的所有后缀 s[l...r] 是否是 forbidden 中的串。为了高效,可以将 forbidden 存入Trie树或哈希集合。
        • 窗口限制: 窗口 s[left...right] 内不包含任何 forbidden 字符串作为其子串。当右指针 r 移动时,如果发现 s[k...r] 是一个 forbidden 串,那么合法的左边界必须在 k 之前。所以,left 必须更新到 k+1
        • 任务: 更新最大窗口长度 right - left + 1
      • 395. 至少有 K 个重复字符的最长子串:

        • 核心限制: 子串中的每个字符出现次数都至少为 K
        • 滑动窗口应用 (分治法为主,非纯滑窗优化):
          1. 统计当前处理字符串 S 中所有字符的频率。
          2. 如果所有字符的频率都 >= K,则 S 本身是一个候选答案,其长度为 len(S)
          3. 否则,找到任意一个出现次数 < K 的字符 c。这个字符 c 不可能出现在最终答案的任何子串中。因此,以字符 c 为分割点,将 S 分割成若干子串,递归地在这些子串上解决问题。
          4. 返回所有递归调用结果中的最大长度。
            (直接用滑动窗口尝试解决此问题较复杂,通常需要枚举不同字符的种类数等辅助手段,分治更直接)。
        • 窗口限制 (分治子问题中): 当前考虑的子串。
        • 任务: 在所有满足"每个字符出现次数都至少为K"的子串中,找到最长的那个。
      • 1763. 最长的美好子字符串 (非暴力做法):

        • 核心限制: "美好子字符串"是指其中出现的每个小写字母,其对应的大写字母也出现,反之亦然。
        • 滑动窗口应用 (分治或枚举所有子串后检查):
          • 分治思路类似395: 检查当前字符串是否美好。如果不是,找到一个"不配对"的字符(例如只有小写a没有大写A,或反之)。这个字符不能在最终美好子串中。以它为分割点递归。
          • 滑动窗口尝试: 右指针扩展。维护窗口内字符的大小写出现情况。如果窗口不是美好的,尝试收缩左边。但这不容易直接判断何时收缩能导向最优解。
          • 非暴力做法通常指分治。
        • 窗口限制 (用于检查子串是否美好): 对于窗口内的每个字符 c,如果 c 是小写,则其大写版本必须在窗口内;如果 c 是大写,则其小写版本必须在窗口内。
        • 任务: 找到最长的美好子字符串。
      • 487. 最大连续 1 的个数 II(会员题): (允许翻转一个0变为1)

        • 核心限制: 最多可以将一个 0 翻转成 1。求最长连续 1 的子数组长度。
        • 滑动窗口应用: 类似1004题,但K=1。
        • 窗口限制: 窗口内 0 的数量 <= 1
        • 任务: 找出满足条件的窗口的最大长度。
      • 159. 至多包含两个不同字符的最长子串(会员题):

        • 核心限制: 子串中最多包含两个不同的字符。
        • 滑动窗口应用: 类似904。
        • 窗口限制: 窗口内不同字符的种类数 <= 2
        • 任务: 找出满足条件的窗口的最大长度。
      • 340. 至多包含 K 个不同字符的最长子串(会员题):

        • 核心限制: 子串中最多包含 K 个不同的字符。
        • 滑动窗口应用: 类似904。
        • 窗口限制: 窗口内不同字符的种类数 <= K
        • 任务: 找出满足条件的窗口的最大长度。
  • §2.2 求最短/最小 (一般题目都有「至少」的要求,满足条件后尝试收缩左边界)

    • 209. 长度最小的子数组:

      • 核心限制: 子数组的和 sum >= target
      • 滑动窗口应用: 右指针扩展,累加窗口和。当 currentSum >= target 时,记录当前窗口长度 right - left + 1,并与已知的最小长度比较更新。然后,左指针收缩,从 currentSum 中减去移出的元素,并继续尝试收缩左指针,只要条件仍满足,就继续更新最小长度。
      • 窗口限制: 窗口内元素之和 >= target
      • 任务: 找出满足条件的窗口的最小长度。
    • 2904. 最短且字典序最小的美丽子字符串 (做到 O(n^2)):

      • 核心限制: 子字符串包含 k 个 '1'。在所有最短的此类子串中,找字典序最小的。
      • 滑动窗口应用: 右指针扩展,计数 '1'。当窗口内 '1' 的数量达到 k 时,这是一个候选的"美丽子字符串"。记录其长度和内容。然后左指针收缩,直到 '1' 的数量小于 k
      • 窗口限制: 窗口内 '1' 的数量等于 k
      • 任务: 比较所有满足条件的窗口:首先按长度取最小,然后在长度相同的里面按字典序取最小。O(N^2) 可能是因为字符串比较。O(N) 的滑窗部分是可能的,但字典序比较可能增加复杂度。
    • 1234. 替换子串得到平衡字符串:

      • 核心限制: 字符串长度为 n (必须是4的倍数)。平衡字符串指 'Q', 'W', 'E', 'R' 四个字符各出现 n/4 次。可以替换原串的一个子串,使整个字符串平衡。求最短的这样的子串长度。

      • 滑动窗口应用:

        1. 先统计整个字符串中各字符的频率,计算出哪些字符"过多"(freq[char] > n/4)。
        2. 目标是找到一个最短的窗口(子串),使得将这个窗口替换后,能补足"过少"的字符,并消耗掉"过多"的字符。更直接地,窗口需要包含足够的"过多"字符,使得窗口外的字符加上窗口内(假设被理想替换后)的字符满足平衡。
        3. 滑动窗口 [left, right]。维护窗口外各字符的频率。检查窗口外的字符加上窗口(假设可以变成任意字符)能否达到平衡。
        • 窗口限制: 对于每个字符 c,窗口外的 count_outside[c] 加上窗口可以提供的补充(如果 count_outside[c] < n/4)或者减去窗口可以消除的多余(如果 count_outside[c] > n/4 但通过窗口消除后能达到 n/4)是否能使所有字符都达到 n/4
          更准确的条件:对于每个字符 Xtotal_freq[X] - window_freq[X](即窗口外的字符 X 的数量)必须 <= n/4。并且,窗口内的字符要能变成所需的字符。
          当窗口外的所有字符都满足 count_outside[c] <= n/4 时,这个窗口是潜在的候选。
        • 任务: 更新最小窗口长度。
    • 2875. 无限数组的最短子数组:

      • 核心限制: 数组 nums 可以无限重复。找到一个和为 target 的最短子数组。

      • 滑动窗口应用:

        1. 计算原数组 nums 的总和 totalSum
        2. target 可以表示为 q * totalSum + remainder_target,其中 remainder_target = target % totalSum (如果 target 为负或 totalSum 为负,取模需小心,通常处理为 (target % totalSum + totalSum) % totalSum)。
        3. 我们需要找到一个子数组和为 remainder_target,这个子数组可能跨越 nums 的边界,所以通常将 nums 复制一次(nums + nums)。
        4. nums + nums (或者逻辑上处理循环) 上使用滑动窗口找和为 remainder_target (以及 remainder_target + totalSum, remainder_target + 2*totalSum 等,但只需考虑 remainder_targetremainder_target + totalSum 覆盖跨一次边界的情况,因为目标是最短 ) 的子数组。
          实际上,任何和为 target 的子数组,其长度 L,对应原数组的 q = L / n 个完整拷贝和剩余长度 L % n 的部分和。
          更常见解法:targetNew = target % sum(nums) (如果 targetNew == 0target > 0targetNew = sum(nums),处理 targetsum 倍数的情况)。然后在 nums+nums 上找和为 targetNew 的最短子数组,长度设为 len1。以及找和为 targetNew + sum(nums) 的最短子数组,长度设为 len2
          最终长度是 (target / sum(nums)) (向下取整,除去已经找到的 targetNewtargetNew+sum(nums) 后还需要的完整数组个数) * n + min_len_for_remainder
        • 窗口限制 (在 nums+nums 上): 窗口和等于某个 current_target (可能是 target % totalSumtarget % totalSum + totalSum 等)。
        • 任务: 找到满足条件的最小窗口长度,并结合完整数组的重复次数计算总长度。
    • 76. 最小覆盖子串:

      • 核心限制: 子串 s 的某个窗口必须包含字符串 t 中的所有字符(且数量要够)。

      • 滑动窗口应用:

        1. 用哈希表 need 记录字符串 t 中每个字符的需求数量。
        2. 用哈希表 window_freq 记录当前滑动窗口 s[left...right] 中字符的频率。
        3. 用一个变量 match_count 记录已满足 need 中字符种类数(或总字符匹配数)。
        4. 右指针 right 扩展,更新 window_freqmatch_count
        5. match_count 达到 t 中不同字符的种类数(且每个字符数量都满足)时,当前窗口是一个覆盖子串。记录其长度,并尝试收缩左指针 left:如果移除 s[left] 后窗口仍然是覆盖子串,则继续收缩;否则,停止收缩 left,并准备移动 right
        • 窗口限制: 窗口内包含了字符串 t 的所有字符,并且每个字符的计数都至少达到了 t 中的需求数量。
        • 任务: 找出满足条件的窗口的最小长度,并返回该子串。
    • 632. 最小区间 (做法不止一种):

      • 核心限制: 给定 k 个排序列表。找到一个最小的数值区间 [min_val, max_val],使得每个列表中至少有一个数落在这个区间内。区间长度定义为 max_val - min_val

      • 滑动窗口应用 (基于合并和排序所有元素):

        1. 将所有列表中的所有元素合并成一个列表,每个元素记录其原始值和所属的列表索引。按元素值排序。
        2. 使用滑动窗口 [left_ptr, right_ptr] 在这个合并排序后的列表上移动。
        3. 维护一个哈希表 list_counts_in_window 记录当前窗口中包含了来自哪些列表的元素以及每个列表包含的元素个数。
        4. 右指针 right_ptr 扩展,将新元素加入窗口,更新 list_counts_in_window
        5. 当窗口包含了来自所有 k 个列表的至少一个元素时(即 list_counts_in_window 的键的数量等于 k,且每个键对应的值都>=1),当前窗口的数值范围是 [merged_list[left_ptr].value, merged_list[right_ptr].value]。这是一个候选区间。记录其长度和边界。然后,尝试收缩左指针 left_ptr,更新 list_counts_in_window,如果窗口仍然满足条件,继续更新和收缩。
        • 窗口限制: 窗口内的元素至少覆盖了所有 k 个原始列表。
        • 任务: 找到满足条件的区间中长度最小的那个。如果长度相同,取起始值较小的。
  • §2.3 求子数组个数

    • §2.3.1 越长越合法 (一般要写 ans += left)

      (灵茶山艾府解释:内层循环结束后,[left,right] 这个子数组是不满足题目要求的,但在退出循环之前的最后一轮循环,[left−1,right] 是满足题目要求的。由于子数组越长,越能满足题目要求,所以除了 [left−1,right],还有 [left−2,right],...,[0,right] 都是满足要求的。也就是说,当右端点固定在 right 时,左端点在 0,1,2,...,left−1 的所有子数组都是满足要求的,这一共有 left 个。)

      • 1358. 包含所有三种字符的子字符串数目:

        • 核心限制: 子字符串需要包含 'a', 'b', 'c' 三种字符。
        • 滑动窗口应用: 右指针 r 扩展。维护窗口 [l,r] 内 'a','b','c' 的计数。当窗口 [l,r] 包含所有三种字符后,所有以 r 为右端点,且左端点在 [0, ..., l] 的子数组都满足。所以 ans += l + 1
          按照灵茶的模板:当 [l,r] 满足条件时,我们尝试将 l 右移。当 l 移动到某个位置使得 [l,r] 不再满足条件时,那么之前的 l_prev (即 l-1) 是使得 [l_prev, r] 满足条件的,并且 [0..l_prev, r] 都满足。
          使用灵茶模板逻辑: 右指针 r 扩展。当窗口 [l,r] 包含三种字符时,尝试将 l 右移并减少对应字符计数,直到不再满足条件。此时,对于固定的 r,有效的左端点是 0l-1 (这里的 l 是第一个使得窗口无效的左边界)。
        • 窗口限制 (用于驱动左指针收缩): 窗口内必须包含 'a', 'b', 和 'c'。当此条件满足时,左指针可以尝试右移。
        • 任务: 统计满足条件的子数组数量。对于每个右端点 r,当窗口 [l,r] 首次包含 a,b,c 后,所有以 r 为右端点,以 0..l 为左端点的子串均满足,贡献 l+1。 或者按灵茶模板,当 [l,r] 刚刚不满足条件时,所有 [0..l-1, r] 满足,贡献 l
      • 2962. 统计最大元素出现至少 K 次的子数组:

        • 核心限制: 子数组中数组的最大元素 max_elem 出现至少 K 次。
        • 滑动窗口应用: 首先找到整个数组的最大元素 max_elem。然后右指针 r 扩展,维护窗口 [l,r]max_elem 的计数 count_max
        • 窗口限制 (用于贡献答案): count_max >= K
        • 任务: 当窗口 [l,r] 满足 count_max >= K 时,所有以 r 为右端点,且左端点在 [0, ..., l] 的子数组也都满足(因为它们包含更多元素,max_elem 的计数不会减少)。所以 ans += l + 1。 (再次,注意与灵茶 ans += left 的细微差别,这取决于 left 的定义)
          使用灵茶模板逻辑:count_max >= K 时,尝试将 l 右移,直到 count_max < K。此时,有效的左端点是 0l-1ans += l
      • 2799. 统计完全子数组的数目 (做到 O(n)):

        • 核心限制: "完全子数组"是指其不同元素的数目等于整个数组中不同元素的数目。
        • 滑动窗口应用: 首先计算整个数组的不同元素总数 total_distinct。右指针 r 扩展,维护窗口 [l,r] 内不同元素的计数 window_distinct 和各元素频率。
        • 窗口限制 (用于贡献答案): window_distinct == total_distinct
        • 任务: 当窗口 [l,r] 满足 window_distinct == total_distinct 时,所有以 r 为右端点,且左端点在 [0, ..., l] 的子数组也都满足。ans += l + 1
          使用灵茶模板逻辑:window_distinct == total_distinct 时,尝试将 l 右移,直到 window_distinct < total_distinct (通过减少频率,如果频率为0则减少 window_distinct)。此时,有效的左端点是 0l-1ans += l
      • 2537. 统计好子数组的数目:

        • 核心限制: "好子数组"是指其中至少有 k 对相同的数 (nums[i] == nums[j] with i < j)
        • 滑动窗口应用: 右指针 r 扩展。维护窗口内元素频率,并计算当前窗口内的数对数量。数对数量可以通过 sum(freq * (freq - 1) / 2) for each freq > 1 来计算。或者,当一个元素加入窗口使其频率从 f-1 变为 f 时,新增的数对是 f-1
        • 窗口限制 (用于贡献答案): 窗口内数对数量 >= k
        • 任务: 当窗口满足条件时,ans += l + 1 (或按灵茶模板 ans += left)。
    • §2.3.2 越短越合法 (一般要写 ans += right - left + 1)

      (内层循环结束后,[left,right] 这个子数组是满足题目要求的。由于子数组越短,越能满足题目要求,所以除了 [left,right],还有 [left+1,right],...,[right,right] 都是满足要求的。也就是说,当右端点固定在 right 时,左端点在 left,left+1,...,right 的所有子数组都是满足要求的,这一共有 right−left+1 个。)

      • 713. 乘积小于 K 的子数组:

        • 核心限制: 子数组中所有元素的乘积严格小于 K。 (假设元素均为正)
        • 滑动窗口应用: 右指针 r 扩展,更新窗口内元素乘积 current_prod。如果 current_prod >= K (且 K > 0),左指针 l 收缩并用 current_prod 除以移出的元素,直到 current_prod < K
        • 窗口限制: 窗口内元素的乘积 < K
        • 任务: 对于每个右端点 r,当窗口 [l,r] 满足 current_prod < K 后(l 已经调整到最左可能的位置),所有以 r 为右端点、以 lr 之间的任意位置为左端点的子数组都满足。这样的子数组有 r - l + 1 个。累加这个数量。
      • 2302. 统计得分小于 K 的子数组数目:

        • 核心限制: 子数组的得分(元素和 * 长度)小于 k
        • 滑动窗口应用: 右指针 r 扩展,维护窗口和 current_sum 和窗口长度 len = r - l + 1。如果 current_sum * len >= k,左指针 l 收缩,更新 current_sumlen,直到条件满足。
        • 窗口限制: current_sum * (r - l + 1) < k
        • 任务: 当窗口 [l,r] 满足条件后,所有以 r 为右端点、以 lr 之间的任意位置为左端点的子数组也都满足。贡献 r - l + 1
      • 2762. 不间断子数组:

        • 核心限制: "不间断子数组"是指其中最大值与最小值之差不超过 2。
        • 滑动窗口应用: 右指针 r 扩展。维护窗口内的最大值和最小值(可以使用 multiset 或两个单调队列)。如果 max_in_window - min_in_window > 2,左指针 l 收缩并更新最值,直到条件满足。
        • 窗口限制: max_in_window - min_in_window <= 2
        • 任务: 当窗口 [l,r] 满足条件后,所有以 r 为右端点、以 lr 之间的任意位置为左端点的子数组也都满足。贡献 r - l + 1
    • §2.3.3 恰好型滑动窗口 (f(k) - f(k+1)atMost(K) - atMost(K-1))

      • 930. 和相同的二元子数组:

        • 核心限制: 子数组元素和恰好等于 goal (数组元素为0或1)。
        • 滑动窗口应用: 转化为 countSubarraysWithSumAtMost(goal) - countSubarraysWithSumAtMost(goal - 1)
        • 窗口限制 (辅助函数 countAtMost(target_sum)): 窗口内元素和 <= target_sum
        • 任务 (辅助函数 countAtMost(target_sum)): 使用 §2.3.2 的逻辑 (ans += right - left + 1) 统计和 <= target_sum 的子数组数量。
        • 最终任务: 计算 countAtMost(goal) - countAtMost(goal - 1)
      • 1248. 统计「优美子数组」:

        • 核心限制: "优美子数组"是指其中包含 k 个奇数。
        • 滑动窗口应用: 将奇数看作1,偶数看作0。问题转化为统计元素和恰好等于 k 的子数组数量。然后使用 countSubarraysWithSumAtMost(k) - countSubarraysWithSumAtMost(k - 1)
        • 窗口限制 (辅助函数 countAtMostOdd(target_odd_count)): 窗口内奇数的数量 <= target_odd_count
        • 任务 (辅助函数 countAtMostOdd(target_odd_count)): 使用 §2.3.2 的逻辑统计奇数个数 <= target_odd_count 的子数组数量。
        • 最终任务: 计算 countAtMostOdd(k) - countAtMostOdd(k - 1)
      • 992. K 个不同整数的子数组:

        • 核心限制: 子数组中恰好有 K 个不同的整数。
        • 滑动窗口应用: 转化为 countSubarraysWithAtMostKDistinct(K) - countSubarraysWithAtMostKDistinct(K - 1)
        • 窗口限制 (辅助函数 countAtMostDistinct(k_val)): 窗口内不同整数的种类数 <= k_val。维护元素频率。
        • 任务 (辅助函数 countAtMostDistinct(k_val)): 使用 §2.3.2 的逻辑统计不同种类数 <= k_val 的子数组数量。
        • 最终任务: 计算 countAtMostDistinct(K) - countAtMostDistinct(K - 1)
  • §2.4 其他(选做)

    • 1438. 绝对差不超过限制的最长连续子数组

      • 核心限制: 子数组中任意两个元素之间的绝对差不超过 limit。这等价于子数组中 (最大值 - 最小值) <= limit
      • 滑动窗口应用: 右指针 r 扩展窗口。同时,需要高效地维护当前窗口 nums[l...r] 内的最大值和最小值。这通常通过两个单调双端队列(一个维护最大值候选,一个维护最小值候选)或一个有序多重集合 (如 C++的 std::multiset 或 Java的 TreeSet) 来实现。
      • 窗口限制: max_in_window - min_in_window <= limit。如果此条件不满足(即差值 > limit),则左指针 l 收缩窗口,并从维护最值的数据结构中移除 nums[l] 的影响,直到条件再次满足。
      • 任务: 在每次窗口有效时,更新记录到的最大窗口长度 r - l + 1
    • 825. 适龄的朋友

      • 核心限制: 人 A (ageA) 不会给 人 B (ageB) 发送好友请求的条件:

        1. ageB <= 0.5 * ageA + 7
        2. ageB > ageA
        3. ageB > 100 && ageA < 100 (这条通常被条件2覆盖,或理解为双向不发送)
          问题是计算总的发送请求数。即 A 会向 B 发送请求,如果上述三个条件 都不 满足。
      • 滑动窗口应用 (在年龄计数或排序年龄上):

        1. 统计每个年龄的出现次数 counts[age] (年龄范围1到120)。
        2. 对于每个 ageA (从1到120,且 counts[ageA] > 0):
          确定 ageB 的有效范围:0.5 * ageA + 7 < ageB <= ageA
          可以使用前缀和数组 prefix_counts (其中 prefix_counts[x] = sum(counts[i] for i <= x)) 来快速计算在有效 ageB 范围内的总人数。
          lower_B_exclusive = floor(0.5 * ageA + 7)
          upper_B_inclusive = ageA
          如果 upper_B_inclusive > lower_B_exclusive,则年龄在 (lower_B_exclusive, upper_B_inclusive] 范围内的人数是 num_valid_B_ages = prefix_counts[upper_B_inclusive] - prefix_counts[lower_B_exclusive]
          ageA 的人会向这些 num_valid_B_ages 人发送请求。总共是 counts[ageA] * num_valid_B_ages
          特别地,如果 ageA 本身也满足作为 ageB 的条件(即 0.5 * ageA + 7 < ageA),那么 ageA 的人不会给自己发送请求,所以需要从 num_valid_B_ages 中减去 counts[ageA] 自身(如果之前计算时包含了它,或者说,counts[ageA] 个人每人会向 num_valid_B_ages - (1 if ageA is in B's range else 0) 范围内的其他人发送请求,并且如果ageA在范围内,还会向 counts[ageA]-1 个同龄人发送)。
          更清晰的计算:对每个 ageA,对每个 ageB(0.5*ageA+7, ageA] 范围内,ans += counts[ageA] * counts[ageB]。如果 ageA == ageB,则实际贡献是 counts[ageA] * (counts[ageA]-1),所以之前的加法多加了 counts[ageA],需要减去。
          或者:ans += counts[ageA] * (num_people_in_valid_B_range_for_A - (1 if ageA_is_valid_for_A else 0))
        • 窗口限制 (逻辑上的): 对于每个 ageA,存在一个有效的 ageB 的"年龄窗口"。
        • 任务: 累加所有可能的请求数。这道题更偏向计数和范围查询,经典的滑动窗口数组遍历不太适用,除非在排序后的年龄数组上用双指针维护 ageB 的范围。
    • 2401. 最长优雅子数组

      • 核心限制: "优雅子数组"是指其中任意两个不同数字 x, y 的按位与(bitwise AND)结果为 0 (x & y == 0)。这等价于子数组中所有数字的二进制表示中,1出现的位置必须是互不重叠的。
      • 滑动窗口应用: 右指针 r 扩展窗口。维护当前窗口 nums[l...r-1] 中所有元素的按位或 current_or_mask。当尝试加入 nums[r] 时:
      • 窗口限制: (current_or_mask & nums[r]) == 0。即 nums[r] 的置位不能与窗口内已有的任何置位冲突。
        如果条件不满足(即 (current_or_mask & nums[r]) != 0),则 nums[r] 与窗口内某个或某些数共享了置位。此时,左指针 l 收缩窗口,将 nums[l]current_or_mask 中"移除"(通过按位异或:current_or_mask ^= nums[l]),然后 l++。重复此过程直到条件满足,才可将 nums[r] 加入,并更新 current_or_mask |= nums[r]
      • 任务: 在每次窗口有效(即可以加入nums[r])后,更新记录到的最大窗口长度 r - l + 1
    • 1156. 单字符重复子串的最大长度

      • 核心限制: 允许执行一次交换操作,目标是得到一个最长的、仅包含单一字符的子串。
      • 滑动窗口应用 (非典型,基于预处理字符块):
        1. 对每个字符 ch ('a' 到 'z') 分别处理:
          a. 统计 ch 在整个字符串中的总出现次数 total_count[ch]
          b. 找出字符串中所有连续的 ch 块,记录它们的起始位置和长度。例如,对 "aaabaaacaa" 和字符 'a',块为 [(0,3), (4,3), (8,2)] (位置,长度)。
        2. 对于每个块,其长度为 L
          一个可能的答案是 min(L + 1, total_count[ch])。 (将块延长1位,如果字符串中有其他 ch 可以换过来;或者如果块本身已经是所有 ch,则长度为 L)。
        3. 对于两个块 block1 (长度 L1) 和 block2 (长度 L2),如果它们之间恰好隔着 一个 其他字符 (例如 "aaaxaaa"):
          一个可能的答案是 min(L1 + L2 + 1, total_count[ch])。 (将中间的那个字符换成 ch,如果字符串中有其他 ch 可以换过来)。
      • 窗口限制: 此处没有传统意义的滑动窗口限制。而是对预处理出的字符块进行分析。
      • 任务: 遍历所有字符和它们对应的块组合情况,找出上述两种情况能得到的最大长度。
    • 424. 替换后的最长重复字符

      • 核心限制: 在子串中,最多执行 k 次操作(将任意字符替换为其他大写英文字母),使得子串变成同一个字符组成的串。求这样的子串的最大可能长度。
      • 滑动窗口应用: 右指针 r 扩展窗口。维护窗口内所有字符的频率,以及当前窗口内出现次数最多的字符的频率 max_freq_in_window
      • 窗口限制: 窗口长度 (r - l + 1) - max_freq_in_window <= k。这个条件表示,窗口中除了出现最多的那个字符(我们希望保留并扩展这种字符)之外的其他字符的数量(这些是需要被替换的字符)不能超过 k
      • 任务: 当上述条件不满足时(即需要替换的字符数 > k),收缩左指针 l,并更新频率和 max_freq_in_window。在每次窗口调整后(特别是扩展右边界后),如果条件满足,则当前的窗口长度 r - l + 1 是一个候选答案,更新全局最大长度。
    • 438. 找到字符串中所有字母异位词

      • 核心限制: 找到字符串 s 中所有子串,这些子串是字符串 p 的一个字母异位词(anagram)。
      • 滑动窗口应用 (定长滑窗最自然):
        • 窗口大小固定为 len(p)
        • s 上滑动这个窗口。维护 p 的字符频率表 need_freq 和当前窗口的字符频率表 window_freq
      • 窗口限制: window_freqneed_freq 完全相同。
      • 任务: 收集所有满足条件的窗口的起始索引。
      • (不定长滑窗思路,如§2.4提及): 如果尝试用不定长窗口,会复杂化。需要扩展窗口,维护频率,当窗口长度达到 len(p) 时检查是否匹配,若不匹配或长度超限则收缩。定长方法更直接。
    • 1712. 将数组分成三个子数组的方案数

      • 核心限制: 将数组 nums 分成三个非空连续子数组 left, mid, right,使得 sum(left) <= sum(mid)sum(mid) <= sum(right)
      • 滑动窗口应用 (基于双指针/二分查找在分割点上,非典型滑窗):
        1. 计算前缀和数组 P
        2. 遍历第二个分割点 j (即 midright 的分界线,mid 的结束索引为 jright 的开始索引为 j+1)。j 的范围从 1n-2 (确保 leftmid 至少一个元素,right 至少一个元素)。
        3. 对于固定的 j
          • sum_right = P[n-1] - P[j]
          • 我们需要找到第一个分割点 i (即 leftmid 的分界线,left 的结束索引为 imid 的开始索引为 i+1)。i 的范围从 0j-1
          • sum_left = P[i]
          • sum_mid = P[j] - P[i]
          • 条件变为:P[i] <= P[j] - P[i] => 2 * P[i] <= P[j] => P[i] <= P[j] / 2
          • P[j] - P[i] <= sum_right => P[i] >= P[j] - sum_right
          • 所以,对于固定的 j,我们需要找到满足 P[j] - sum_right <= P[i] <= P[j] / 2i 的数量,其中 0 <= i < j
          • 这可以在 P[0...j-1] 上通过两次二分查找(lower_bound 找满足 >= P[j] - sum_right 的最小 i_startupper_bound 找满足 <= P[j] / 2 的最大 i_end)来确定 i 的有效范围 [i_start, i_end]。符合条件的 i 的数量是 max(0, i_end - i_start + 1)
      • 窗口限制 (逻辑上的): 对于固定的第二分割点 j,第一分割点 i 的选择构成一个"有效范围",这个范围由 sum_left <= sum_midsum_mid <= sum_right 共同约束。
      • 任务: 累加对每个 j 找到的有效 i 的数量。
    • 1918. 第 K 小的子数组和(会员题)

      • 核心限制: 找到所有连续子数组的和中的第 K 小的那个和。
      • 滑动窗口应用 (作为二分查找答案的辅助函数):
        1. 这个问题的标准解法是"答案上的二分查找"。可能和的范围是 [min_array_element, sum_all_elements]
        2. 二分查找一个候选答案 current_max_sum
        3. 需要一个辅助函数 count_subarrays_with_sum_less_than_or_equal_to(max_s),这个函数可以用滑动窗口实现:
          • 右指针 r 扩展,维护当前窗口 nums[l...r] 的和 window_sum
          • 窗口限制 (辅助函数内): window_sum <= max_s。如果 window_sum > max_s,则左指针 l 收缩,直到条件满足。
          • 当窗口 [l,r] 满足 window_sum <= max_s 后,所有以 r 为右端点、以 lr 之间的任意位置为左端点的子数组也都满足。这样的子数组有 r - l + 1 个。累加这个数量。
        4. 如果 count_subarrays_with_sum_less_than_or_equal_to(current_max_sum) >= K,说明真正的第K小和可能更小或就是 current_max_sum,所以尝试缩小上限。否则,说明 current_max_sum 太小了,需要增大下限。
      • 任务: 通过二分查找找到满足条件的最小 current_max_sum

滑动窗口核心限制类型总结

  1. 元素唯一性/频率限制:

    • 窗口内元素完全唯一: 窗口中不允许出现重复的元素。
      • 示例: 3. 无重复字符的最长子串, 1695. 删除子数组的最大得分, 2461. 长度为 K 子数组中的最大和 (定长), 1100. 长度为 K 的无重复字符子串 (会员题, 定长)。
    • 窗口内单个/所有元素出现频率不超过特定值: 窗口中每个(或特定)元素出现的次数有上限。
      • 示例: 3090. 每个字符最多出现两次的最长子字符串 (每个字符 <= 2次), 2958. 最多 K 个重复元素的最长子数组 (任意元素 <= K次), 395. 至少有 K 个重复字符的最长子串 (分治,子问题中字符频率 >= K)。
    • 窗口内元素频率等于特定值: (常用于"完全"或"等计数"子串)
      • 示例: 2953. 统计完全子字符串 (每个字符出现次数都是 k), 2067. 等计数子串的数量 (每个出现过的字母出现次数都相同)。
  2. 元素种类限制:

    • 窗口内不同元素的种类数量有上限: 窗口中不同元素的类别不能超过一个给定数目。
      • 示例: 904. 水果成篮 (<= 2种), 159. 至多包含两个不同字符的最长子串 (会员题, <= 2种), 340. 至多包含 K 个不同字符的最长子串 (会员题, <= K种), 1297. 子串的最大出现次数 (不同字符 <= maxLetters, 定长)。
    • 窗口内不同元素的种类数量有下限: 窗口中不同元素的类别必须达到一个给定数目。
      • 示例: 2841. 几乎唯一子数组的最大和 (不同元素 >= m, 定长)。
    • 窗口内不同元素的种类数量等于特定值:
      • 示例: 2799. 统计完全子数组的数目 (不同元素数 == 整个数组不同元素数), 992. K 个不同整数的子数组 (恰好K种,通过 atMost(K) - atMost(K-1) 实现)。
  3. 特定元素数量/计数限制:

    • 窗口内某一特定元素的数量有上限/下限/等于特定值: 关注窗口内如 '0', '1', 元音,或需要被修改的特定字符的数量。
      • 上限示例: 1493. 删掉一个元素以后全为 1 的最长子数组 (0的个数 <= 1), 1004. 最大连续 1 的个数 III (0的个数 <= K), 2024. 考试的最大困扰度 (T或F的个数 <= k), 2379. 得到 K 个黑块的最少涂色次数 ('W'的个数, 定长,求最小值)。
      • 下限示例: 2962. 统计最大元素出现至少 K 次的子数组 (最大元素出现 >= K次)。
      • 等于特定值示例: 2904. 最短且字典序最小的美丽子字符串 ('1'的个数 == k), 1248. 统计「优美子数组」(奇数个数 == K,转化后)。
    • 窗口内满足特定条件的元素对/组合的数量限制:
      • 示例: 2537. 统计好子数组的数目 (相同数对 >= k)。
  4. 代价/和/积/平均值的限制或目标:

    • 窗口内元素的和/积/代价总和有上限/下限/等于特定值:
      • 上限示例: 1208. 尽可能使字符串相等 (总代价 <= maxCost), 713. 乘积小于 K 的子数组 (乘积 < K), 2302. 统计得分小于 K 的子数组数目 (和 * 长度 < k)。
      • 下限示例: 209. 长度最小的子数组 (和 >= target)。
      • 等于特定值示例: 1658. 将 x 减到 0 的最小操作数 (转化后:和 == target), 930. 和相同的二元子数组 (和 == K,转化后)。
    • 窗口内元素的平均值达到某个阈值或目标:
      • 示例: 643. 子数组最大平均数 I (求最大平均值, 定长), 1343. 大小为 K 且平均值大于等于阈值的子数组数目 (平均值 >= threshold, 定长)。
  5. 元素间关系/结构限制:

    • 窗口内元素的最大值与最小值之差有上限:
      • 示例: 1438. 绝对差不超过限制的最长连续子数组 (max - min <= limit), 2762. 不间断子数组 (max - min <= 2)。
    • 窗口内元素满足特定排列/顺序/模式:
      • 示例: 567. 字符串的排列 (字符频率与s1一致, 定长), 438. 找到字符串中所有字母异位词 (字符频率与p一致, 定长), 30. 串联所有单词的子串 (由words中单词恰好一次拼接, 定长), 1888. 使二进制字符串字符交替的最少反转次数 (与"0101..."或"1010..."模式比较, 定长)。
    • 窗口内元素不包含禁止的子结构:
      • 示例: 2781. 最长合法子字符串的长度 (不含 forbidden 子串)。
  6. 固定窗口大小 (通常作为首要限制,并与其他限制组合):

    • 描述: 许多问题的核心操作是基于一个固定长度为 k (或由题目参数确定的其他固定值) 的窗口。算法通常是初始化第一个窗口,然后通过移除左端元素、加入右端元素的方式滑动,同时维护窗口内的状态或检查条件。
    • 示例: 几乎所有标记为"定长滑动窗口"的题目,如 1456, 643, 2379, 2461, 2090, 2134, 1052, 1652, 2653, 567, 438, 2269, 1984。
    • 注意: 固定大小本身是一个限制,但通常会伴随上述1-5类限制之一或多个,用于对窗口内容进行评估。
  7. 转换型问题:

    • 描述: 原始问题表述可能不直接是滑动窗口,但可以通过数学转换、逆向思考或问题重构,将其转化为一个标准的滑动窗口问题。
    • 示例: 1423. 可获得的最大点数 (取两端k个 -> 求中间n-k个的最小和), 1658. 将 x 减到 0 的最小操作数 (从两端减x -> 求中间和为sum-x的最长子数组), "恰好K个"问题通过 atMost(K) - atMost(K-1) 解决 (e.g., 992, 930, 1248)。
  8. 特定查找/统计任务 (在有效窗口内):

    • 描述: 窗口的限制可能简单(如固定大小),但核心任务是在这个窗口内执行特定的查找或统计。
    • 示例: 2653. 滑动子数组的美丽值 (找第x小负数, 定长), 1297. 子串的最大出现次数 (统计特定子串出现次数, 定长+频率限制), 1456. 定长子串中元音的最大数目 (统计元音, 定长)。

总结与使用:

理解这些限制类型有助于在面对新问题时,快速识别其是否适合滑动窗口,并判断窗口应该如何移动(固定长度滑动,还是根据条件动态伸缩左右边界),以及需要维护哪些状态信息(如频率表、和、种类计数、最值等)。很多题目会组合多种限制类型。

相关推荐
vibag11 分钟前
第十六届蓝桥杯复盘
java·算法·蓝桥杯·竞赛
Owen_Q13 分钟前
Leetcode百题斩-回溯
算法·leetcode·职场和发展
珹洺24 分钟前
计算机操作系统(十一)调度器/调度程序,闲逛调度与调度算法的评价指标
android·java·算法
理论最高的吻1 小时前
HJ33 整数与IP地址间的转换【牛客网】
c++·算法·牛客网·ip地址转换
我漫长的孤独流浪1 小时前
STL中的Vector(顺序表)
开发语言·c++·算法
通达的K2 小时前
Java的常见算法和Lambda表达式
java·数据结构·算法
软考真题app2 小时前
软件设计师考试三大核心算法考点深度解析(红黑树 / 拓扑排序 / KMP 算法)真题考点分析——求三连
java·开发语言·算法·软考·软件设计师
YGGP2 小时前
动态规划之打家劫舍模型
算法·动态规划
虾球xz2 小时前
游戏引擎学习第301天:使用精灵边界进行排序
学习·算法·游戏引擎
白熊1882 小时前
【机器学习基础】机器学习入门核心算法:线性回归(Linear Regression)
人工智能·算法·机器学习·回归·线性回归