文章目录
-
- 摘要/引言
- 第一部分:滑动窗口算法基础
-
- 1.1 定义
- 1.2 特点
- 1.3 适用场景
- 第二部分:滑动窗口算法的实现
-
- 2.1 数据结构选择
- 2.2 算法步骤
- 2.3 代码示例
- 第三部分:滑动窗口算法的应用
-
- 3.1 问题分析
- 3.2 应用案例
- 3.3 解决方案
- 第四部分:滑动窗口算法的优化
-
- 4.1 优化策略
- 4.2 优化示例
- 第五部分:滑动窗口算法的变体
-
- 5.1 扩展概念
-
- 特点:
- 应用场景:
- 5.2 变体应用
-
- 数据流中的异常检测
- 动态时间序列分析
- 可变长度子数组问题
- 5.3 变体算法实现
-
- 代码分析:
- 第六部分:LeetCodeHOT100
-
-
- 题目一:3. 无重复字符的最长子串
- 题目二:438. 找到字符串中所有字母异位词
-
摘要/引言
滑动窗口算法是一种在数据流或数组中处理连续子数组或子串问题的有效方法。它广泛应用于解决各种问题,如最大子数组和、无重复字符的最长子串等。本文将深入探讨滑动窗口算法的基本概念、实现方式、应用案例、优化策略以及面临的挑战。
第一部分:滑动窗口算法基础
1.1 定义
滑动窗口是一种迭代方法,通过在数据集合上滑动一个固定大小的窗口来遍历数据,同时保持窗口内的数据满足特定条件。
1.2 特点
- 连续性:窗口内的元素在原始数据中是连续的。
- 动态性:窗口可以根据需要动态调整大小。
- 适用性:适用于解决数据流或大数据量问题。
1.3 适用场景
滑动窗口算法适用于需要连续序列统计的问题,如求连续子数组的最大和、最小平均值等。
第二部分:滑动窗口算法的实现
2.1 数据结构选择
通常使用数组、链表或队列作为滑动窗口的数据结构。双端队列(Deque)特别适合实现滑动窗口,因为它允许在两端快速添加和删除元素。
2.2 算法步骤
- 初始化窗口的起始和结束位置,以及用于存储窗口信息的数据结构。
- 根据窗口内的数据计算所需的统计信息。
- 根据算法需求,滑动窗口到下一个位置,并更新统计信息。
2.3 代码示例
def max_subarray_sum(nums):
max_sum = float('-inf')
current_sum = 0
start = 0
for end in range(len(nums)):
current_sum += nums[end]
while current_sum > max_sum and start <= end:
max_sum = current_sum
current_sum -= nums[start]
start += 1
return max_sum
第三部分:滑动窗口算法的应用
3.1 问题分析
将问题转化为滑动窗口问题通常涉及确定窗口的大小和滑动规则,以及如何在窗口移动过程中更新所需的统计数据。
3.2 应用案例
- 最大子数组和:使用滑动窗口找到数组中和最大的连续子数组。
- 最长无重复字符的子串:使用滑动窗口和哈希集合来解决。
3.3 解决方案
针对每个案例,分析问题并提供滑动窗口算法的实现方法。
第四部分:滑动窗口算法的优化
4.1 优化策略
- 空间优化:减少额外空间的使用,例如使用变量代替数据结构。
- 时间优化:优化窗口的滑动逻辑,减少不必要的计算。
4.2 优化示例
提供优化后的滑动窗口算法实现,展示优化效果。
第五部分:滑动窗口算法的变体
5.1 扩展概念
滑动窗口算法的变体之一是膨胀滑动窗口(Expanding Window),它与传统滑动窗口的主要区别在于窗口大小不是固定的。膨胀滑动窗口可以根据特定条件动态调整大小,这使得它能够适应更多种类的问题。
特点:
- 动态大小:窗口可以根据数据特征或算法需求动态扩展或收缩。
- 灵活性:适用于解决那些窗口大小会影响结果的问题。
- 条件触发:窗口的扩展和收缩通常由特定条件触发,如遇到特定的数据值。
应用场景:
- 数据流中的模式匹配:在数据流中寻找特定模式,模式的长度可能是变化的。
- 动态时间窗口的问题:如根据时间戳动态计算数据窗口。
5.2 变体应用
膨胀滑动窗口算法可以应用于以下场景:
数据流中的异常检测
在数据流中,某些异常模式可能具有可变长度,使用膨胀滑动窗口可以动态调整以匹配这些模式。
动态时间序列分析
在时间序列分析中,根据事件的发生频率动态调整分析的时间窗口大小,以捕捉有意义的模式。
可变长度子数组问题
解决一些需要考虑不同长度子数组的问题,如最大和子数组问题,但最大值可能不在固定长度的窗口内。
5.3 变体算法实现
以下是膨胀滑动窗口算法的一个简单实现示例,该示例寻找一个字符串中最长的重复子串:
def longest_dup_substring(s):
start = 0
end = 1
longest = ""
seen = {
}
while end <= len(s):
# 检查当前子串是否已经出现过
window = s[start:end]
if window in seen:
# 如果出现过,更新最长重复子串
if end - start > len(longest):
longest = window
start = seen[window]
end = start + len(longest) # 移动窗口,尝试扩展长度
else:
seen[window] = start
start += 1
end += 1
return longest
# 示例使用
s = "banana"
print(longest_dup_substring(s)) # 输出 "anana"
代码分析:
- 这个算法使用一个字典
seen
来存储遇到的子串及其起始索引。 - 通过不断增加
end
来尝试扩展窗口,同时保持start
不变。 - 当找到一个重复的子串时,更新最长重复子串,并根据已存储的索引重置
start
和end
。
第六部分:LeetCodeHOT100
题目一:3. 无重复字符的最长子串
- 问题描述:给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
- 解决方案:通过滑动窗口内的 left 和 right 指针来维护一个不包含重复字符的子串。当遇到重复字符时,移动 left 指针到重复字符的下一个位置,从而排除掉窗口中的重复字符。
- 代码示例:
int lengthOfLongestSubstring(char* s) {
int n = strlen(s);
int charIndex[128]; // ASCII 128字符的索引表
for (int i = 0; i < 128; i++) {
charIndex[i] = -1; // 初始化索引表为-1
}
int left = 0, maxLength = 0;
for (int right = 0; right < n; right++) {
char currentChar = s[right];
if (charIndex[currentChar] >= left) {
left = charIndex[currentChar] + 1; // 更新左边界
}
charIndex[currentChar] = right; // 更新当前字符的最新索引
int currentLength = right - left + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
}
}
return maxLength;
}
- 结果分析:
题目二:438. 找到字符串中所有字母异位词
- 问题描述:给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。
- 解决方案:
- 统计目标字符串 p 中每个字符的出现次数。
- 使用滑动窗口遍历字符串 s,窗口大小与 p 相同。
- 在窗口内统计字符的出现次数。
- 每次比较窗口内的字符计数与 p 的字符计数是否相等,若相等则找到一个异位词子串。
- 记录所有符合条件的子串的起始索引。
- 代码示例:
class Solution(object):
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
if not s or not p:
return []
p_length = len(p)
s_length = len(s)
if p_length > s_length:
return []
result = []
p_count = dict.fromkeys(p, 0)
for char in p:
p_count[char] += 1
s_window = {
}
left = 0
for right in range(s_length):
s_window[s[right]] = s_window.get(s[right], 0) + 1
if right - left + 1 == p_length:
if s_window == p_count:
result.append(left)
s_window[s[left]] -= 1
if s_window[s[left]] == 0:
del s_window[s[left]]
left += 1
return result
- 结果分析: