[Go版]算法通关村第十六关青铜——原来滑动窗口如此简单

目录

  • 应用了滑动窗口思想的示例
  • 滑动窗口的基本思想
  • 两个入门题
    • 题目:子数组最大平均数
      • 思路分析:计算滑动窗口值总和为 +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库和应用示例:

  1. Golang原生库中的使用:在Go的标准库中,time.Tickertime.Tick等定时器机制中,可以看到类似滑动窗口的思想,用于定期触发任务。
  2. 熔断器库github.com/afex/hystrix-go:这个库实现了熔断器模式,用于防止故障服务影响到整个系统,其背后的思想就涉及到窗口和阈值的管理。
  3. 流量控制与限流github.com/juju/ratelimit:这个库提供了限流功能,可以用于实现滑动窗口限流策略。
  4. 分布式系统:在分布式系统中,可以使用滑动窗口来实现分布式锁、限流等策略,一些基于Raft等一致性算法的库可能会使用这种思想。

滑动窗口的基本思想

滑动窗口的思想非常简单,如下图所示,加入窗口的大小是3,当不断有新数据来时,我们会维护一个大小为3的一个区间,超过3的就将新的放入老的移走。

这个过程有点像火车在铁轨上跑,原始数据可能保存在一个很大的空间里,但是我们标记的小区间就像一列火车,一直向前走。
[Go版]算法通关村第十六关青铜——原来滑动窗口如此简单_第1张图片
从上面的图可以看到,所谓窗口就是建立两个索引,left和right,并且保持right-left=3,然后一边遍历序列,一边寻找,每改变一次就标记一下当前区间的最大值就行了。

这个例子已经告诉我们了什么是窗口、什么是窗口的滑动:

  • 窗口:窗口其实就是两个变量left和right之间的元素,也可以理解为一个区间。窗口大小可能固定,也可能变化,如果是固定大小的,那么自然要先确定窗口是否越界,再执行逻辑处理。如果不是固定的,就要先判断是否满足要求,再执行逻辑处理。
  • 滑动:说明这个窗口是移动的,事实上移动的仍然是left和right两个变量,而不是序列中的元素。当变量移动的时,其中间的元素必然会发生变化,因此就有了这种不断滑动的效果。

那双指针和滑动窗口啥区别呢?根据性质我们可以看到,滑动窗口是双指针的一种类型,主要关注两个指针之间元素的情况,因此范围更小一些,而双指针的应用范围更大。

两个入门题

滑动窗口在不同的题目里,根据窗口大小变或者不变,有两种类型。这里我们就看两个基本的题目。

题目:子数组最大平均数

题目链接:LeetCode-643. 子数组最大平均数 I
[Go版]算法通关村第十六关青铜——原来滑动窗口如此简单_第2张图片

思路分析:计算滑动窗口值总和为 +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. 最长连续递增序列
[Go版]算法通关村第十六关青铜——原来滑动窗口如此简单_第3张图片

思路分析:当滑动窗口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
}

你可能感兴趣的:(算法与数据结构,算法,golang)