在 Go 中实现滑动窗口可以利用双指针或者队列进行灵活调整。滑动窗口特别适合解决一维数据结构中的子区间问题,例如字符串和数组。以下是滑动窗口的详解,包括通用框架、经典题目,以及实际场景中的应用。
1. 滑动窗口的核心思想
滑动窗口的目的是维护一个动态区间,满足某些条件的同时尽量高效地更新结果:
- 扩展窗口:右指针
right向右移动扩展窗口。 - 收缩窗口:左指针
left向右移动缩小窗口。 - 记录结果:在满足条件时,更新解或记录窗口状态。
2. 滑动窗口通用框架(Go 实现)
滑动窗口的通用代码框架如下:
go
func slidingWindow(s string, t string) string {
left, right := 0, 0 // 窗口左右指针
window := make(map[byte]int) // 当前窗口字符计数
need := make(map[byte]int) // 目标字符计数
valid := 0 // 满足条件的字符数
// 初始化目标计数
for i := 0; i < len(t); i++ {
need[t[i]]++
}
// 滑动窗口
for right < len(s) {
c := s[right]
right++ // 扩展窗口
if need[c] > 0 {
window[c]++
if window[c] == need[c] {
valid++
}
}
// 判断是否收缩窗口
for valid == len(need) {
// 更新结果或其他逻辑
// 左边字符移出窗口
d := s[left]
left++ // 收缩窗口
if need[d] > 0 {
if window[d] == need[d] {
valid--
}
window[d]--
}
}
}
return "" // 返回结果
}3. 滑动窗口经典问题
3.1 最小覆盖子串
问题描述:在字符串 s 中找到包含 t 中所有字符的最小子串。
go
func minWindow(s string, t string) string {
need := make(map[byte]int)
window := make(map[byte]int)
for i := 0; i < len(t); i++ {
need[t[i]]++
}
left, right, valid := 0, 0, 0
start, minLen := 0, len(s)+1
for right < len(s) {
c := s[right]
right++
if need[c] > 0 {
window[c]++
if window[c] == need[c] {
valid++
}
}
for valid == len(need) {
if right-left < minLen {
start = left
minLen = right - left
}
d := s[left]
left++
if need[d] > 0 {
if window[d] == need[d] {
valid--
}
window[d]--
}
}
}
if minLen == len(s)+1 {
return ""
}
return s[start : start+minLen]
}3.2 无重复字符的最长子串
问题描述:在字符串 s 中找到不含重复字符的最长子串。
go
func lengthOfLongestSubstring(s string) int {
window := make(map[byte]int)
left, right := 0, 0
maxLength := 0
for right < len(s) {
c := s[right]
right++
window[c]++
for window[c] > 1 { // 出现重复字符
d := s[left]
left++
window[d]--
}
if right-left > maxLength {
maxLength = right - left
}
}
return maxLength
}3.3 找所有字母异位词
问题描述:在字符串 s 中找到所有 p 的异位词(排列组合)的起始索引。
go
func findAnagrams(s string, p string) []int {
need := make(map[byte]int)
window := make(map[byte]int)
for i := 0; i < len(p); i++ {
need[p[i]]++
}
left, right, valid := 0, 0, 0
res := []int{}
for right < len(s) {
c := s[right]
right++
if need[c] > 0 {
window[c]++
if window[c] == need[c] {
valid++
}
}
for right-left >= len(p) {
if valid == len(need) {
res = append(res, left)
}
d := s[left]
left++
if need[d] > 0 {
if window[d] == need[d] {
valid--
}
window[d]--
}
}
}
return res
}3.4 最大连续 1 的个数(带限制条件)
问题描述:数组中最多允许翻转 k 个 0,找到连续 1 的最大长度。
go
func longestOnes(nums []int, k int) int {
left, right := 0, 0
zeroCount := 0
maxLen := 0
for right < len(nums) {
if nums[right] == 0 {
zeroCount++
}
right++
for zeroCount > k {
if nums[left] == 0 {
zeroCount--
}
left++
}
if right-left > maxLen {
maxLen = right - left
}
}
return maxLen
}4. 使用场景
4.1 网络流量监控
- 滑动窗口用于记录一段时间内的请求数量或流量,判断是否超出阈值。
- 实现时可用队列记录每次请求的时间戳,动态调整窗口。
4.2 日志处理
- 滑动窗口用于统计日志文件中,某段时间的特定事件发生频率。
- 窗口动态调整以适配不同的时间范围。
4.3 股票价格分析
- 滑动窗口用于计算某个时间窗口内的最大收益或价格波动范围。
4.4 视频流缓冲
- 滑动窗口用于监控视频数据缓存量,动态调整缓冲区大小以避免卡顿。
滑动窗口是一种非常通用的算法技巧。在实际应用中,重点在于动态维护窗口的状态,根据场景需求设计扩展和收缩的条件。结合具体问题,可以进一步优化代码性能!如果有具体场景需要实现,可以继续讨论!