滑动窗口算法(Sliding Window)
一、详细讲解
A、一句话总结
滑动窗口算法是一种通过维护一个动态窗口来解决问题的技巧,窗口在数据上“滑动”,逐步找到最优解。
B、核心思想
想象你在看一列火车,火车窗口只能看到一部分车厢。滑动窗口算法就是通过调整窗口的起点和终点,找到你感兴趣的部分(比如最长的连续车厢、最短的覆盖范围等)。
C、适用场景
滑动窗口算法通常用于解决以下问题:
- 子数组/子字符串问题:比如找最长的无重复字符子串、最短的覆盖子串等。
- 连续区间问题:比如找满足条件的连续子数组。
- 优化问题:比如在固定窗口大小内找最大值、最小值或平均值。
D、滑动窗口的两种类型
1. 固定大小的窗口
- 窗口大小固定,比如每次只看连续的 3 个元素。
- 例子:计算数组中每个长度为 k 的子数组的平均值。
2. 可变大小的窗口
- 窗口大小不固定,根据条件动态调整。
- 例子:找最长的无重复字符子串。
E、滑动窗口的基本步骤
-
初始化窗口:
- 定义窗口的起点(
left
)和终点(right
),通常初始化为 0。 - 定义一些辅助变量(比如当前窗口的和、最大值、最小值等)。
- 定义窗口的起点(
-
滑动窗口:
- 移动右边界(
right
),扩大窗口,直到满足某个条件。 - 移动左边界(
left
),缩小窗口,直到不满足条件。 - 在滑动过程中,记录需要的结果(比如最大长度、最小长度等)。
- 移动右边界(
-
返回结果:
- 根据滑动过程中记录的结果,返回最终答案。
F、关键特点
- 高效:通过滑动窗口,避免了重复计算,时间复杂度通常为 (O(n))。
- 灵活:适用于多种问题,尤其是需要处理连续区间的问题。
- 双指针:滑动窗口通常用双指针(
left
和right
)来实现。
G、举个实际例子
问题:找最长的无重复字符子串
- 比如字符串
"abcabcbb"
,最长的无重复字符子串是"abc"
,长度为 3。 - 滑动窗口的过程:
- 初始化窗口:
left = 0
,right = 0
。 - 移动
right
,直到遇到重复字符(比如'a'
重复)。 - 移动
left
,缩小窗口,直到重复字符被排除。 - 重复上述过程,记录最大长度。
- 初始化窗口:
H、滑动窗口的优缺点
优点
- 时间复杂度低,通常为 (O(n))。
- 代码简洁,逻辑清晰。
- 适用于多种连续区间问题。
缺点
- 需要仔细设计窗口的移动规则。
- 对非连续区间问题不适用。
I、时间复杂度和空间复杂度
- 时间复杂度:通常为 (O(n)),因为每个元素最多被访问两次(
left
和right
各一次)。 - 空间复杂度:通常为 (O(1)) 或 (O(n)),取决于是否需要额外的数据结构(比如哈希表)。
J、实际应用场景
- 字符串处理:比如找最长的无重复字符子串、最短的覆盖子串。
- 数组处理:比如找满足条件的连续子数组、固定大小的滑动窗口最大值。
- 数据分析:比如计算滑动窗口内的平均值、最大值、最小值。
K、总结
滑动窗口算法就像“望远镜”,通过调整窗口的大小和位置,找到你感兴趣的部分。它的核心是双指针和动态调整窗口,适合解决连续区间问题,既高效又灵活!
二、滑动窗口算法示例:找最长的无重复字符子串
A、问题描述
给定一个字符串,找出其中不含有重复字符的最长子串的长度。
示例
输入:"abcabcbb"
输出:3
解释:最长的无重复字符子串是 "abc"
,长度为 3。
B、滑动窗口算法实现
核心思路
- 使用双指针
left
和right
表示窗口的左右边界。 - 使用哈希集合(
set
)记录窗口内的字符,确保无重复。 - 移动
right
扩大窗口,如果遇到重复字符,则移动left
缩小窗口。 - 在滑动过程中,记录窗口的最大长度。
C、分步图示演示
以下是对字符串 "abcabcbb"
的分步演示:
初始状态
字符串:a b c a b c b b
索引: 0 1 2 3 4 5 6 7
窗口:[]
left = 0, right = 0
最大长度:0
步骤 1:right = 0,字符 ‘a’
- 字符 ‘a’ 不在集合中,加入集合。
- 更新窗口:
[a]
- 更新最大长度:1
窗口:[a]
left = 0, right = 0
最大长度:1
步骤 2:right = 1,字符 ‘b’
- 字符 ‘b’ 不在集合中,加入集合。
- 更新窗口:
[a, b]
- 更新最大长度:2
窗口:[a, b]
left = 0, right = 1
最大长度:2
步骤 3:right = 2,字符 ‘c’
- 字符 ‘c’ 不在集合中,加入集合。
- 更新窗口:
[a, b, c]
- 更新最大长度:3
窗口:[a, b, c]
left = 0, right = 2
最大长度:3
步骤 4:right = 3,字符 ‘a’
- 字符 ‘a’ 已经在集合中,需要移动
left
。 - 移动
left
到 1,移除字符 ‘a’。 - 更新窗口:
[b, c, a]
- 最大长度保持不变:3
窗口:[b, c, a]
left = 1, right = 3
最大长度:3
步骤 5:right = 4,字符 ‘b’
- 字符 ‘b’ 已经在集合中,需要移动
left
。 - 移动
left
到 2,移除字符 ‘b’。 - 更新窗口:
[c, a, b]
- 最大长度保持不变:3
窗口:[c, a, b]
left = 2, right = 4
最大长度:3
步骤 6:right = 5,字符 ‘c’
- 字符 ‘c’ 已经在集合中,需要移动
left
。 - 移动
left
到 3,移除字符 ‘c’。 - 更新窗口:
[a, b, c]
- 最大长度保持不变:3
窗口:[a, b, c]
left = 3, right = 5
最大长度:3
步骤 7:right = 6,字符 ‘b’
- 字符 ‘b’ 已经在集合中,需要移动
left
。 - 移动
left
到 5,移除字符 ‘a’ 和 ‘b’。 - 更新窗口:
[c, b]
- 最大长度保持不变:3
窗口:[c, b]
left = 5, right = 6
最大长度:3
步骤 8:right = 7,字符 ‘b’
- 字符 ‘b’ 已经在集合中,需要移动
left
。 - 移动
left
到 7,移除字符 ‘c’ 和 ‘b’。 - 更新窗口:
[b]
- 最大长度保持不变:3
窗口:[b]
left = 7, right = 7
最大长度:3
D、最终结果
遍历结束后,最大长度为 3
,对应的子串是 "abc"
。
E、Python3 代码实现
def longest_substring_without_repeating_characters(s):left = 0 # 窗口左边界max_length = 0 # 最大长度char_set = set() # 记录窗口内的字符for right in range(len(s)):while s[right] in char_set: # 如果字符重复,移动左边界char_set.remove(s[left])left += 1char_set.add(s[right]) # 将当前字符加入集合max_length = max(max_length, right - left + 1) # 更新最大长度return max_length# 示例
s = "abcabcbb"
print("最长无重复字符子串的长度:", longest_substring_without_repeating_characters(s))
F、总结
通过滑动窗口算法,我们可以高效地找到最长的无重复字符子串。滑动窗口的核心是双指针和动态调整窗口,适合解决连续区间问题。希望这个示例和图解对你有帮助!
© 著作权归作者所有