主题: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. 子串的最大出现次数
- 核心限制: 子串长度在
minSize
和maxSize
之间。子串中的不同字符数不能超过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_freq
与need
完全一致。 - 任务: 如果存在任何一个窗口满足上述条件,则返回
true
,否则返回false
。
- 核心限制: 判断
-
438. 找到字符串中所有字母异位词
- 核心限制: 找到
s
中所有是p
的字母异位词的子串的起始索引。 - 滑动窗口应用: 维护一个长度为
len(p)
的窗口在s
上滑动。使用频率数组或哈希表比较字符频率。 - 窗口限制: 窗口大小严格等于
len(p)
。 - 附加条件检查: 当前窗口内字符的频率分布与
p
的字符频率分布完全一致。 - 任务: 收集所有满足条件的窗口的起始索引。
- 核心限制: 找到
-
30. 串联所有单词的子串
- 核心限制: 子串必须由
words
数组中所有单词串联而成,每个单词恰好出现一次,顺序不限,且单词之间没有其他字符。 - 滑动窗口应用: 设
wordLen = len(words)
,numWords = len(words)
。窗口总长度固定为totalLen = wordLen * numWords
。由于单词长度固定,需要从0
到wordLen - 1
共wordLen
个不同的起始偏移量开始滑动。对每个起始偏移量,以wordLen
为步长检查窗口。在主窗口内部,再将窗口划分为numWords
个小段,每段长度为wordLen
。 - 窗口限制: 主窗口大小严格等于
totalLen
。 - 附加条件检查: 主窗口可以被精确划分为
numWords
个长度为wordLen
的小段,这些小段形成的集合(考虑频率)与words
数组形成的集合(考虑频率)相同。通常使用哈希表来匹配这些小段。 - 任务: 收集所有满足条件的窗口的起始索引。
- 核心限制: 子串必须由
-
2156. 查找给定哈希值的子串
- 核心限制: 子串长度为
k
。其哈希值(按特定公式使用power
和modulo
计算)等于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
中所有长度在1
到log2(N)+1
(N的二进制表示的最大可能长度)之间的子串。将这些子串视为二进制数,转换为十进制存入一个集合。 - 窗口限制: 窗口长度从
1
迭代到len(bin(N))
。对于每个长度,滑动窗口遍历s
。 - 任务: 检查集合中是否包含了从
1
到N
的所有数字。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
- 核心限制: 存在两个不同下标
i
和j
,使得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]
内的元素。 - 任务: 判断是否存在这样的
i
和j
。
- 核心限制: 存在两个不同下标
-
二、不定长滑动窗口
窗口大小不固定,根据窗口内元素满足的条件动态调整。
-
§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'。 - 滑动窗口应用: 此问题需要分别计算两种情况,然后取最大值:
- 目标为全'T':右指针扩展,计数窗口内'F'的数量。若'F'的数量超过
k
,左指针收缩窗口并更新'F'的计数。 - 目标为全'F':右指针扩展,计数窗口内'T'的数量。若'T'的数量超过
k
,左指针收缩窗口并更新'T'的计数。
- 目标为全'T':右指针扩展,计数窗口内'F'的数量。若'F'的数量超过
- 窗口限制 (针对目标'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
。 - 任务: 更新最大窗口长度。
- 核心限制: "半重复子字符串"是指其中至多有一对相邻字符是相同的 (例如, "aabbc" 中的 "aa" 或 "bb" 是一对,"aaabbc" 中的 "aaa" 包含两对 "aa")。题目应指至多一个位置
-
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
的计算。当成本超k
,left
右移。 - 任务: 更新最大窗口长度
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
。求能摘到的最大水果数。可以向左或向右,或先一边再另一边。 - 滑动窗口应用: 这不是典型的线性滑动窗口。可以枚举两种主要策略:
- 只往一个方向走。
- 先往一个方向走
x
步,再掉头往另一个方向走y
步。约束是x+y <= k
(如果只考虑单程距离) 或2x+y <= k
(先左后右,左边往返) 或x+2y <= k
(先右后左,右边往返)。
对每种策略,确定能到达的左右边界,然后累加该区间的水果。
前缀和+枚举/滑窗变种: 计算水果数量的前缀和。枚举向左走的距离left_dist
(0 tok
)。则剩余步数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
。求最多能同时看到的点数。 - 滑动窗口应用:
- 将所有点转换为相对于
location
的极坐标角度。与location
重合的点特殊处理(总是可见)。 - 对所有角度进行排序。
- 由于视野是环形的(例如,从350度到10度是合法的),将排序后的角度数组复制一份,每个角度加上360度,然后拼接到原数组后面。得到一个长度为
2m
的角度数组。 - 在这个扩展的角度数组上使用滑动窗口。右指针
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
。 - 滑动窗口应用 (分治法为主,非纯滑窗优化):
- 统计当前处理字符串
S
中所有字符的频率。 - 如果所有字符的频率都
>= K
,则S
本身是一个候选答案,其长度为len(S)
。 - 否则,找到任意一个出现次数
< K
的字符c
。这个字符c
不可能出现在最终答案的任何子串中。因此,以字符c
为分割点,将S
分割成若干子串,递归地在这些子串上解决问题。 - 返回所有递归调用结果中的最大长度。
(直接用滑动窗口尝试解决此问题较复杂,通常需要枚举不同字符的种类数等辅助手段,分治更直接)。
- 统计当前处理字符串
- 窗口限制 (分治子问题中): 当前考虑的子串。
- 任务: 在所有满足"每个字符出现次数都至少为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
次。可以替换原串的一个子串,使整个字符串平衡。求最短的这样的子串长度。 -
滑动窗口应用:
- 先统计整个字符串中各字符的频率,计算出哪些字符"过多"(
freq[char] > n/4
)。 - 目标是找到一个最短的窗口(子串),使得将这个窗口替换后,能补足"过少"的字符,并消耗掉"过多"的字符。更直接地,窗口需要包含足够的"过多"字符,使得窗口外的字符加上窗口内(假设被理想替换后)的字符满足平衡。
- 滑动窗口
[left, right]
。维护窗口外各字符的频率。检查窗口外的字符加上窗口(假设可以变成任意字符)能否达到平衡。
- 窗口限制: 对于每个字符
c
,窗口外的count_outside[c]
加上窗口可以提供的补充(如果count_outside[c] < n/4
)或者减去窗口可以消除的多余(如果count_outside[c] > n/4
但通过窗口消除后能达到n/4
)是否能使所有字符都达到n/4
。
更准确的条件:对于每个字符X
,total_freq[X] - window_freq[X]
(即窗口外的字符X
的数量)必须<= n/4
。并且,窗口内的字符要能变成所需的字符。
当窗口外的所有字符都满足count_outside[c] <= n/4
时,这个窗口是潜在的候选。 - 任务: 更新最小窗口长度。
- 先统计整个字符串中各字符的频率,计算出哪些字符"过多"(
-
-
2875. 无限数组的最短子数组:
-
核心限制: 数组
nums
可以无限重复。找到一个和为target
的最短子数组。 -
滑动窗口应用:
- 计算原数组
nums
的总和totalSum
。 target
可以表示为q * totalSum + remainder_target
,其中remainder_target = target % totalSum
(如果target
为负或totalSum
为负,取模需小心,通常处理为(target % totalSum + totalSum) % totalSum
)。- 我们需要找到一个子数组和为
remainder_target
,这个子数组可能跨越nums
的边界,所以通常将nums
复制一次(nums + nums
)。 - 在
nums + nums
(或者逻辑上处理循环) 上使用滑动窗口找和为remainder_target
(以及remainder_target + totalSum
,remainder_target + 2*totalSum
等,但只需考虑remainder_target
和remainder_target + totalSum
覆盖跨一次边界的情况,因为目标是最短 ) 的子数组。
实际上,任何和为target
的子数组,其长度L
,对应原数组的q = L / n
个完整拷贝和剩余长度L % n
的部分和。
更常见解法:targetNew = target % sum(nums)
(如果targetNew == 0
且target > 0
则targetNew = sum(nums)
,处理target
为sum
倍数的情况)。然后在nums+nums
上找和为targetNew
的最短子数组,长度设为len1
。以及找和为targetNew + sum(nums)
的最短子数组,长度设为len2
。
最终长度是(target / sum(nums))
(向下取整,除去已经找到的targetNew
或targetNew+sum(nums)
后还需要的完整数组个数)* n + min_len_for_remainder
。
- 窗口限制 (在 nums+nums 上): 窗口和等于某个
current_target
(可能是target % totalSum
或target % totalSum + totalSum
等)。 - 任务: 找到满足条件的最小窗口长度,并结合完整数组的重复次数计算总长度。
- 计算原数组
-
-
76. 最小覆盖子串:
-
核心限制: 子串
s
的某个窗口必须包含字符串t
中的所有字符(且数量要够)。 -
滑动窗口应用:
- 用哈希表
need
记录字符串t
中每个字符的需求数量。 - 用哈希表
window_freq
记录当前滑动窗口s[left...right]
中字符的频率。 - 用一个变量
match_count
记录已满足need
中字符种类数(或总字符匹配数)。 - 右指针
right
扩展,更新window_freq
和match_count
。 - 当
match_count
达到t
中不同字符的种类数(且每个字符数量都满足)时,当前窗口是一个覆盖子串。记录其长度,并尝试收缩左指针left
:如果移除s[left]
后窗口仍然是覆盖子串,则继续收缩;否则,停止收缩left
,并准备移动right
。
- 窗口限制: 窗口内包含了字符串
t
的所有字符,并且每个字符的计数都至少达到了t
中的需求数量。 - 任务: 找出满足条件的窗口的最小长度,并返回该子串。
- 用哈希表
-
-
632. 最小区间 (做法不止一种):
-
核心限制: 给定
k
个排序列表。找到一个最小的数值区间[min_val, max_val]
,使得每个列表中至少有一个数落在这个区间内。区间长度定义为max_val - min_val
。 -
滑动窗口应用 (基于合并和排序所有元素):
- 将所有列表中的所有元素合并成一个列表,每个元素记录其原始值和所属的列表索引。按元素值排序。
- 使用滑动窗口
[left_ptr, right_ptr]
在这个合并排序后的列表上移动。 - 维护一个哈希表
list_counts_in_window
记录当前窗口中包含了来自哪些列表的元素以及每个列表包含的元素个数。 - 右指针
right_ptr
扩展,将新元素加入窗口,更新list_counts_in_window
。 - 当窗口包含了来自所有
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
,有效的左端点是0
到l-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
。此时,有效的左端点是0
到l-1
。ans += 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
)。此时,有效的左端点是0
到l-1
。ans += l
。
-
2537. 统计好子数组的数目:
- 核心限制: "好子数组"是指其中至少有
k
对相同的数(nums[i] == nums[j] with i < j)
。 - 滑动窗口应用: 右指针
r
扩展。维护窗口内元素频率,并计算当前窗口内的数对数量。数对数量可以通过sum(freq * (freq - 1) / 2)
for eachfreq > 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
为右端点、以l
到r
之间的任意位置为左端点的子数组都满足。这样的子数组有r - l + 1
个。累加这个数量。
- 核心限制: 子数组中所有元素的乘积严格小于
-
2302. 统计得分小于 K 的子数组数目:
- 核心限制: 子数组的得分(元素和 * 长度)小于
k
。 - 滑动窗口应用: 右指针
r
扩展,维护窗口和current_sum
和窗口长度len = r - l + 1
。如果current_sum * len >= k
,左指针l
收缩,更新current_sum
和len
,直到条件满足。 - 窗口限制:
current_sum * (r - l + 1) < k
。 - 任务: 当窗口
[l,r]
满足条件后,所有以r
为右端点、以l
到r
之间的任意位置为左端点的子数组也都满足。贡献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
为右端点、以l
到r
之间的任意位置为左端点的子数组也都满足。贡献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) 发送好友请求的条件:
ageB <= 0.5 * ageA + 7
ageB > ageA
ageB > 100 && ageA < 100
(这条通常被条件2覆盖,或理解为双向不发送)
问题是计算总的发送请求数。即 A 会向 B 发送请求,如果上述三个条件 都不 满足。
-
滑动窗口应用 (在年龄计数或排序年龄上):
- 统计每个年龄的出现次数
counts[age]
(年龄范围1到120)。 - 对于每个
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. 单字符重复子串的最大长度
- 核心限制: 允许执行一次交换操作,目标是得到一个最长的、仅包含单一字符的子串。
- 滑动窗口应用 (非典型,基于预处理字符块):
- 对每个字符
ch
('a' 到 'z') 分别处理:
a. 统计ch
在整个字符串中的总出现次数total_count[ch]
。
b. 找出字符串中所有连续的ch
块,记录它们的起始位置和长度。例如,对 "aaabaaacaa" 和字符 'a',块为[(0,3), (4,3), (8,2)]
(位置,长度)。 - 对于每个块,其长度为
L
:
一个可能的答案是min(L + 1, total_count[ch])
。 (将块延长1位,如果字符串中有其他ch
可以换过来;或者如果块本身已经是所有ch
,则长度为L
)。 - 对于两个块
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_freq
与need_freq
完全相同。 - 任务: 收集所有满足条件的窗口的起始索引。
- (不定长滑窗思路,如§2.4提及): 如果尝试用不定长窗口,会复杂化。需要扩展窗口,维护频率,当窗口长度达到
len(p)
时检查是否匹配,若不匹配或长度超限则收缩。定长方法更直接。
- 核心限制: 找到字符串
-
1712. 将数组分成三个子数组的方案数
- 核心限制: 将数组
nums
分成三个非空连续子数组left
,mid
,right
,使得sum(left) <= sum(mid)
且sum(mid) <= sum(right)
。 - 滑动窗口应用 (基于双指针/二分查找在分割点上,非典型滑窗):
- 计算前缀和数组
P
。 - 遍历第二个分割点
j
(即mid
和right
的分界线,mid
的结束索引为j
,right
的开始索引为j+1
)。j
的范围从1
到n-2
(确保left
和mid
至少一个元素,right
至少一个元素)。 - 对于固定的
j
:sum_right = P[n-1] - P[j]
。- 我们需要找到第一个分割点
i
(即left
和mid
的分界线,left
的结束索引为i
,mid
的开始索引为i+1
)。i
的范围从0
到j-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] / 2
的i
的数量,其中0 <= i < j
。 - 这可以在
P[0...j-1]
上通过两次二分查找(lower_bound
找满足>= P[j] - sum_right
的最小i_start
,upper_bound
找满足<= P[j] / 2
的最大i_end
)来确定i
的有效范围[i_start, i_end]
。符合条件的i
的数量是max(0, i_end - i_start + 1)
。
- 计算前缀和数组
- 窗口限制 (逻辑上的): 对于固定的第二分割点
j
,第一分割点i
的选择构成一个"有效范围",这个范围由sum_left <= sum_mid
和sum_mid <= sum_right
共同约束。 - 任务: 累加对每个
j
找到的有效i
的数量。
- 核心限制: 将数组
-
1918. 第 K 小的子数组和(会员题)
- 核心限制: 找到所有连续子数组的和中的第
K
小的那个和。 - 滑动窗口应用 (作为二分查找答案的辅助函数):
- 这个问题的标准解法是"答案上的二分查找"。可能和的范围是
[min_array_element, sum_all_elements]
。 - 二分查找一个候选答案
current_max_sum
。 - 需要一个辅助函数
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
为右端点、以l
到r
之间的任意位置为左端点的子数组也都满足。这样的子数组有r - l + 1
个。累加这个数量。
- 右指针
- 如果
count_subarrays_with_sum_less_than_or_equal_to(current_max_sum) >= K
,说明真正的第K小和可能更小或就是current_max_sum
,所以尝试缩小上限。否则,说明current_max_sum
太小了,需要增大下限。
- 这个问题的标准解法是"答案上的二分查找"。可能和的范围是
- 任务: 通过二分查找找到满足条件的最小
current_max_sum
。
- 核心限制: 找到所有连续子数组的和中的第
-
滑动窗口核心限制类型总结
-
元素唯一性/频率限制:
- 窗口内元素完全唯一: 窗口中不允许出现重复的元素。
- 示例: 3. 无重复字符的最长子串, 1695. 删除子数组的最大得分, 2461. 长度为 K 子数组中的最大和 (定长), 1100. 长度为 K 的无重复字符子串 (会员题, 定长)。
- 窗口内单个/所有元素出现频率不超过特定值: 窗口中每个(或特定)元素出现的次数有上限。
- 示例: 3090. 每个字符最多出现两次的最长子字符串 (每个字符 <= 2次), 2958. 最多 K 个重复元素的最长子数组 (任意元素 <= K次), 395. 至少有 K 个重复字符的最长子串 (分治,子问题中字符频率 >= K)。
- 窗口内元素频率等于特定值: (常用于"完全"或"等计数"子串)
- 示例: 2953. 统计完全子字符串 (每个字符出现次数都是 k), 2067. 等计数子串的数量 (每个出现过的字母出现次数都相同)。
- 窗口内元素完全唯一: 窗口中不允许出现重复的元素。
-
元素种类限制:
- 窗口内不同元素的种类数量有上限: 窗口中不同元素的类别不能超过一个给定数目。
- 示例: 904. 水果成篮 (<= 2种), 159. 至多包含两个不同字符的最长子串 (会员题, <= 2种), 340. 至多包含 K 个不同字符的最长子串 (会员题, <= K种), 1297. 子串的最大出现次数 (不同字符 <=
maxLetters
, 定长)。
- 示例: 904. 水果成篮 (<= 2种), 159. 至多包含两个不同字符的最长子串 (会员题, <= 2种), 340. 至多包含 K 个不同字符的最长子串 (会员题, <= K种), 1297. 子串的最大出现次数 (不同字符 <=
- 窗口内不同元素的种类数量有下限: 窗口中不同元素的类别必须达到一个给定数目。
- 示例: 2841. 几乎唯一子数组的最大和 (不同元素 >=
m
, 定长)。
- 示例: 2841. 几乎唯一子数组的最大和 (不同元素 >=
- 窗口内不同元素的种类数量等于特定值:
- 示例: 2799. 统计完全子数组的数目 (不同元素数 == 整个数组不同元素数), 992. K 个不同整数的子数组 (恰好K种,通过 atMost(K) - atMost(K-1) 实现)。
- 窗口内不同元素的种类数量有上限: 窗口中不同元素的类别不能超过一个给定数目。
-
特定元素数量/计数限制:
- 窗口内某一特定元素的数量有上限/下限/等于特定值: 关注窗口内如 '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)。
- 窗口内某一特定元素的数量有上限/下限/等于特定值: 关注窗口内如 '0', '1', 元音,或需要被修改的特定字符的数量。
-
代价/和/积/平均值的限制或目标:
- 窗口内元素的和/积/代价总和有上限/下限/等于特定值:
- 上限示例: 1208. 尽可能使字符串相等 (总代价 <=
maxCost
), 713. 乘积小于 K 的子数组 (乘积 < K), 2302. 统计得分小于 K 的子数组数目 (和 * 长度 < k)。 - 下限示例: 209. 长度最小的子数组 (和 >=
target
)。 - 等于特定值示例: 1658. 将 x 减到 0 的最小操作数 (转化后:和 ==
target
), 930. 和相同的二元子数组 (和 == K,转化后)。
- 上限示例: 1208. 尽可能使字符串相等 (总代价 <=
- 窗口内元素的平均值达到某个阈值或目标:
- 示例: 643. 子数组最大平均数 I (求最大平均值, 定长), 1343. 大小为 K 且平均值大于等于阈值的子数组数目 (平均值 >=
threshold
, 定长)。
- 示例: 643. 子数组最大平均数 I (求最大平均值, 定长), 1343. 大小为 K 且平均值大于等于阈值的子数组数目 (平均值 >=
- 窗口内元素的和/积/代价总和有上限/下限/等于特定值:
-
元素间关系/结构限制:
- 窗口内元素的最大值与最小值之差有上限:
- 示例: 1438. 绝对差不超过限制的最长连续子数组 (max - min <=
limit
), 2762. 不间断子数组 (max - min <= 2)。
- 示例: 1438. 绝对差不超过限制的最长连续子数组 (max - min <=
- 窗口内元素满足特定排列/顺序/模式:
- 示例: 567. 字符串的排列 (字符频率与s1一致, 定长), 438. 找到字符串中所有字母异位词 (字符频率与p一致, 定长), 30. 串联所有单词的子串 (由words中单词恰好一次拼接, 定长), 1888. 使二进制字符串字符交替的最少反转次数 (与"0101..."或"1010..."模式比较, 定长)。
- 窗口内元素不包含禁止的子结构:
- 示例: 2781. 最长合法子字符串的长度 (不含
forbidden
子串)。
- 示例: 2781. 最长合法子字符串的长度 (不含
- 窗口内元素的最大值与最小值之差有上限:
-
固定窗口大小 (通常作为首要限制,并与其他限制组合):
- 描述: 许多问题的核心操作是基于一个固定长度为
k
(或由题目参数确定的其他固定值) 的窗口。算法通常是初始化第一个窗口,然后通过移除左端元素、加入右端元素的方式滑动,同时维护窗口内的状态或检查条件。 - 示例: 几乎所有标记为"定长滑动窗口"的题目,如 1456, 643, 2379, 2461, 2090, 2134, 1052, 1652, 2653, 567, 438, 2269, 1984。
- 注意: 固定大小本身是一个限制,但通常会伴随上述1-5类限制之一或多个,用于对窗口内容进行评估。
- 描述: 许多问题的核心操作是基于一个固定长度为
-
转换型问题:
- 描述: 原始问题表述可能不直接是滑动窗口,但可以通过数学转换、逆向思考或问题重构,将其转化为一个标准的滑动窗口问题。
- 示例: 1423. 可获得的最大点数 (取两端k个 -> 求中间n-k个的最小和), 1658. 将 x 减到 0 的最小操作数 (从两端减x -> 求中间和为sum-x的最长子数组), "恰好K个"问题通过
atMost(K) - atMost(K-1)
解决 (e.g., 992, 930, 1248)。
-
特定查找/统计任务 (在有效窗口内):
- 描述: 窗口的限制可能简单(如固定大小),但核心任务是在这个窗口内执行特定的查找或统计。
- 示例: 2653. 滑动子数组的美丽值 (找第x小负数, 定长), 1297. 子串的最大出现次数 (统计特定子串出现次数, 定长+频率限制), 1456. 定长子串中元音的最大数目 (统计元音, 定长)。
总结与使用:
理解这些限制类型有助于在面对新问题时,快速识别其是否适合滑动窗口,并判断窗口应该如何移动(固定长度滑动,还是根据条件动态伸缩左右边界),以及需要维护哪些状态信息(如频率表、和、种类计数、最值等)。很多题目会组合多种限制类型。