【算法】-单调队列

目录

什么是单调队列

区域内最大值

区域内最小值


什么是单调队列

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

        其内部元素在队列内维持单调特性(递增或递减),类似单调栈,但是与单调栈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
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值