当前位置: 首页 > article >正文

【算法】-单调队列

目录

什么是单调队列

区域内最大值

区域内最小值


什么是单调队列

        说到单调队列,其实就是一个双端队列, 顾名思义,单调队列的重点分为「单调」和「队列」。「单调」指的是元素的「规律」——递增(或递减)。「队列」指的是元素只能从队头和队尾进行操作。

        其内部元素在队列内维持单调特性(递增或递减),类似单调栈,但是与单调栈FILO不同的是队列的FIFO的特性,配合滑动窗口的思想,可以解决区间内最值的问题。


区域内最大值

        例题:239. 滑动窗口最大值 - 力扣(LeetCode)

        我们来简单分析一下,题目要求的是滑动窗口内最大值,也就是区间内的最大值。这时候如果我们用暴力解法,维护一个双指针的滑窗和窗口内最大值,当窗口右移时,我们要比较新加入元素与窗口内最大值,选择是否更新。

        如果是更新最大值的情况还好办,但是我们设想一下不理想的情况,新加入的元素不比窗口最大值大,同时移出窗口的元素恰恰是窗口最大值!那么这样一来,区间内的最大值是什么呢?这是没办法直接得出的,在这里还要遍历整个窗口重新寻找窗口最大值,太浪费时间了。

        这时候用单调队列就能很好的解决问题,因为队列元素的单调性,使得获取最值和维护更新最值的操作自然一体。单调队列在入队时,比较来判断加入后会不会破坏单调性,不会则加入,会就出队原来的元素直到加入后满足单调性,这一行为可以抽象为滑动窗口滑的过程,同时还有注意根据题意在窗口大小k范围更新记录答案。

        总的来说,单调队列就是以下3个步骤:

  1. 入(元素进入队尾,同时维护队列单调性
  2. 出(元素离开队首
  3. 记录/维护答案(根据队首
func maxSlidingWindow(nums []int, k int) []int {
    ans := make([]int, 0, len(nums)-k+1) // 预先分配好空间
    q := []int{}
    for i, x := range nums {
        // 1. 入
        for len(q) > 0 && nums[q[len(q)-1]] <= x {
            q = q[:len(q)-1] // 维护 q 的单调性
        }
        q = append(q, i) // 入队
        // 2. 出
        if i-q[0] >= k { // 队首已经离开窗口了
            q = q[1:] // Go 的切片是 O(1) 的
        }
        // 3. 记录答案
        if i >= k-1 {
            // 由于队首到队尾单调递减,所以窗口最大值就是队首
            ans = append(ans, nums[q[0]])
        }
    }
    return ans
}

区域内最小值

        例题:2398. 预算内的最多机器人数目 - 力扣(LeetCode)

        题目要求机器人连续运行,看成一个连续子数组,题目要求计算最长子数组长度。枚举子数组右端点 right,我们需要知道此时左端点 left 的最小值,这样子数组尽量长。

        由于有 budget 的限制,所以 right 越大,left 也越大,有单调性,可以用滑动窗口解决。套路如下:

  1. 入:chargeTimes[right] 进入窗口时,弹出队尾的 ≤chargeTimes[right] 的元素。
  2. 出:如果总开销超过 budget,则不断移出左端点,直到总开销不超过 budget。特别地,如果左端点恰好等于队首,则弹出队首。
  3. 更新答案:用窗口长度 right−left+1 更新答案的最大值。

        注意:为了方便判断队首是否要出队,单调队列中保存的是下标。

func maximumRobots(chargeTimes, runningCosts []int, budget int64) (ans int) {
    q := []int{}
    sum := int64(0)
    left := 0
    for right, t := range chargeTimes {
        // 1. 入
        for len(q) > 0 && t >= chargeTimes[q[len(q)-1]] {
            q = q[:len(q)-1]
        }
        q = append(q, right)
        sum += int64(runningCosts[right])
        // 2. 出
        for len(q) > 0 && int64(chargeTimes[q[0]])+int64(right-left+1)*sum > budget {
            if q[0] == left {
                q = q[1:]
            }
            sum -= int64(runningCosts[left])
            left++
        }
        // 3. 更新答案
        ans = max(ans, right-left+1)
    }
    return
}


http://www.kler.cn/a/302640.html

相关文章:

  • 第三十一天|贪心算法| 56. 合并区间,738.单调递增的数字 , 968.监控二叉树
  • 【机器学习】如何配置anaconda环境(无脑版)
  • 缓存与数据库不一致的解决方案:深入理解与实践
  • Java面向对象编程进阶之包装类
  • 【121. 买卖股票的最佳时机】——贪心算法/动态规划
  • Microsoft 365 Exchange如何设置可信发件IP白名单
  • 数据库系统 第43节 数据库复制
  • LabVIEW回转马达试验系统
  • Git撤销add
  • Flutter类
  • Vue:通过js控制css变量 - 一键修改全局样式
  • Docker 常用命令(未完待续...)
  • 外贸网站建设该怎么做
  • Certbot 生成 SSL 证书并配置自动续期
  • android 发一个可以下载的的android studio历史版本
  • 深度学习——pycharm配置远程服务器(蓝耘GPU智算云)
  • JavaScript拷贝的艺术:玩转深拷贝和浅拷贝
  • Arcgis字段计算器:随机生成规定范围内的数字
  • vue2中使用web worker启动定时器
  • 25届计算机专业选题推荐-基于微信小程序的校园快递驿站代收管理系统
  • 修改docker的默认存储位置及镜像存储位置
  • 无人机低空安全管控系统技术详解
  • 2024年9月13日随笔
  • c++中extern “C“的作用及理解
  • 【FFMPEG】FFplay音视频同步分析(下)
  • 仕考网:2525年国考时间是什么时候?