在数组章节我们说过很多算法会大量移动数组中的元素,而且会频繁移动,这导致执行效率低下、执行超时。使用两个变量能比较好的解决很多问题,在《一维数组》和《链表》章节我们介绍了很多典型例子,于是这种方式就慢慢演化成了“双指针思想”。
在很多应用中将其进一步完善,便形成了滑动窗口思想,学过计算机网络的同学都知道滑动窗口协议(Sliding Window Protocol),该协议是TCP实现流量控制等的核心策略之一。事实上在于流量控制、熔断、限流、超时等场景下都会首先从滑动窗口的角度来思考问题。
以下是一些应用了滑动窗口思想的Go库和应用示例:
time.Ticker
和time.Tick
等定时器机制中,可以看到类似滑动窗口的思想,用于定期触发任务。github.com/afex/hystrix-go
:这个库实现了熔断器模式,用于防止故障服务影响到整个系统,其背后的思想就涉及到窗口和阈值的管理。github.com/juju/ratelimit
:这个库提供了限流功能,可以用于实现滑动窗口限流策略。滑动窗口的思想非常简单,如下图所示,加入窗口的大小是3,当不断有新数据来时,我们会维护一个大小为3的一个区间,超过3的就将新的放入老的移走。
这个过程有点像火车在铁轨上跑,原始数据可能保存在一个很大的空间里,但是我们标记的小区间就像一列火车,一直向前走。
从上面的图可以看到,所谓窗口就是建立两个索引,left和right,并且保持right-left=3,然后一边遍历序列,一边寻找,每改变一次就标记一下当前区间的最大值就行了。
这个例子已经告诉我们了什么是窗口、什么是窗口的滑动:
那双指针和滑动窗口啥区别呢?根据性质我们可以看到,滑动窗口是双指针的一种类型,主要关注两个指针之间元素的情况,因此范围更小一些,而双指针的应用范围更大。
滑动窗口在不同的题目里,根据窗口大小变或者不变,有两种类型。这里我们就看两个基本的题目。
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
}
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
}