目录
- 应用了滑动窗口思想的示例
- 滑动窗口的基本思想
- 两个入门题
- 题目:子数组最大平均数
- 思路分析:计算滑动窗口值总和为 +right -left
- 复杂度:时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)
- Go代码
- 题目:最长连续递增序列
- 思路分析:当滑动窗口right 移动状态没有升序时,left=right
- 复杂度:时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)
- Go代码
应用了滑动窗口思想的示例
在数组章节我们说过很多算法会大量移动数组中的元素,而且会频繁移动,这导致执行效率低下、执行超时。使用两个变量能比较好的解决很多问题,在《一维数组》和《链表》章节我们介绍了很多典型例子,于是这种方式就慢慢演化成了“双指针思想”。
在很多应用中将其进一步完善,便形成了滑动窗口思想,学过计算机网络的同学都知道滑动窗口协议(Sliding Window Protocol),该协议是TCP实现流量控制等的核心策略之一。事实上在于流量控制、熔断、限流、超时等场景下都会首先从滑动窗口的角度来思考问题。
以下是一些应用了滑动窗口思想的Go库和应用示例:
- Golang原生库中的使用:在Go的标准库中,
time.Ticker
和time.Tick
等定时器机制中,可以看到类似滑动窗口的思想,用于定期触发任务。 - 熔断器库:
github.com/afex/hystrix-go
:这个库实现了熔断器模式,用于防止故障服务影响到整个系统,其背后的思想就涉及到窗口和阈值的管理。 - 流量控制与限流:
github.com/juju/ratelimit
:这个库提供了限流功能,可以用于实现滑动窗口限流策略。 - 分布式系统:在分布式系统中,可以使用滑动窗口来实现分布式锁、限流等策略,一些基于Raft等一致性算法的库可能会使用这种思想。
滑动窗口的基本思想
滑动窗口的思想非常简单,如下图所示,加入窗口的大小是3,当不断有新数据来时,我们会维护一个大小为3的一个区间,超过3的就将新的放入老的移走。
这个过程有点像火车在铁轨上跑,原始数据可能保存在一个很大的空间里,但是我们标记的小区间就像一列火车,一直向前走。
从上面的图可以看到,所谓窗口就是建立两个索引,left和right,并且保持right-left=3,然后一边遍历序列,一边寻找,每改变一次就标记一下当前区间的最大值就行了。
这个例子已经告诉我们了什么是窗口、什么是窗口的滑动:
- 窗口:窗口其实就是两个变量left和right之间的元素,也可以理解为一个区间。窗口大小可能固定,也可能变化,如果是固定大小的,那么自然要先确定窗口是否越界,再执行逻辑处理。如果不是固定的,就要先判断是否满足要求,再执行逻辑处理。
- 滑动:说明这个窗口是移动的,事实上移动的仍然是left和right两个变量,而不是序列中的元素。当变量移动的时,其中间的元素必然会发生变化,因此就有了这种不断滑动的效果。
📢 那双指针和滑动窗口啥区别呢?根据性质我们可以看到,滑动窗口是双指针的一种类型,主要关注两个指针之间元素的情况,因此范围更小一些,而双指针的应用范围更大。
两个入门题
滑动窗口在不同的题目里,根据窗口大小变或者不变,有两种类型。这里我们就看两个基本的题目。
题目:子数组最大平均数
题目链接:LeetCode-643. 子数组最大平均数 I
思路分析:计算滑动窗口值总和为 +right -left
复杂度:时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)
Go代码
func findMaxAverage(nums []int, k int) float64 {
length := len(nums)
if length < k {
return 0
}
winLength := 0 //窗口和
// 计算第一个窗口的值和
for i:=0; i<k; i++ {
winLength += nums[i]
}
max := winLength
for i:=k; i<length; i++ {
winLength = winLength + nums[i] - nums[i-k]
max = getMax(max, winLength)
}
return float64(max) / float64(k)
}
func getMax(a,b int) int {
if a > b {
return a
}
return b
}
题目:最长连续递增序列
题目链接:LeetCode-674. 最长连续递增序列
思路分析:当滑动窗口right 移动状态没有升序时,left=right
复杂度:时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)
Go代码
func findLengthOfLCIS(nums []int) int {
length := len(nums)
if length == 0 || length == 1 {
return length
}
max := 1
for left,right:=0,1; right < length; {
if nums[right] <= nums[right-1] {
left = right
}
right++
max = getMax(max, right-left)
}
return max
}
func getMax(a,b int) int {
if a > b {
return a
}
return b
}