十三、leetcode刷题之【单调队列】

[TOC]

基础知识

队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。

单调队列不是单纯的给队列中元素排序,那和优先级队列没有什么区别了。

设计单调队列的时候,pop,和push操作要保持如下规则:
pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。

单调队列模板

队列里存数值


type StrictQueue struct {
    Queue []int
}

func NewStrictQueue() *StrictQueue {
    return &StrictQueue{
        Queue: make([]int, 0),
    }
}

// 如果队列不为空且要移除的元素为队列中最大值,则将其弹出,否则不做任何操作,因为我们关心的是队列中的最大值
func (sq *StrictQueue) Pop(value int) {
    if !sq.IsEmpty() && sq.Queue[0] == value {
        sq.Queue = sq.Queue[1:]
    }
}

// 如果队列不为空且要添加的元素大于队列入口元素,则将队列入口元素弹出,直到添加的元素小于等于队列入口元素为止,以保证队列是严格单调递减的。
func (sq *StrictQueue) Push(value int) {
    for !sq.IsEmpty() && sq.Queue[sq.Size()-1] < value {
        sq.Queue = sq.Queue[:sq.Size()-1]
    }
    sq.Queue = append(sq.Queue, value)
}

// 返回队列中的第一个元素,此处是最大值
func (sq *StrictQueue) Peek() int {
    return sq.Queue[0]
}

func (sq *StrictQueue) IsEmpty() bool {
    return len(sq.Queue) == 0
}

func (sq *StrictQueue) Size() int {
    return len(sq.Queue)
}

队列里存index


type StrictQueue struct {
    Queue []int
}

func NewStrictQueue() *StrictQueue {
    return &StrictQueue{
        Queue: make([]int, 0),
    }
}

// peekFirst()方法用于返回此双端队列表示的队列的第一个元素,但不删除该元素。
func (sq *StrictQueue) peekFirst() int {
    if !sq.IsEmpty() {
        return sq.Queue[0]
    }
    return -1
}

// peekLast()方法最后一个元素或结尾元素,但不会从列表中删除最后一个元素。
func (sq *StrictQueue) peekLast() int {
    if !sq.IsEmpty() {
        return sq.Queue[len(sq.Queue)-1]
    }
    return -1
}

// pollFirst 检索并删除此列表的第一个元素,如果此列表为空,则返回null。
func (sq *StrictQueue) pollFirst() {
    if !sq.IsEmpty() {
        sq.Queue = sq.Queue[1:]
    }
}

// pollLast 检索并删除此列表的最后一个元素,如果此列表为空,则返回null。
func (sq *StrictQueue) pollLast() {
    if !sq.IsEmpty() {
        sq.Queue = sq.Queue[:len(sq.Queue)-1]
    }
}

// offerLast 用于在此Deque的末尾添加特定元素。
func (sq *StrictQueue) offerLast(value int) {
    sq.Queue = append(sq.Queue, value)
}

func (sq *StrictQueue) IsEmpty() bool {
    return len(sq.Queue) == 0
}

func (sq *StrictQueue) Size() int {
    return len(sq.Queue)
}

Leetcode刷题

239. 滑动窗口最大值【困难】

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

输入:nums = [1], k = 1
输出:[1]
// ===========================  解法一:单调队列,队列里存的是数值  =================
//链接:https://leetcode-cn.com/problems/sliding-window-maximum/solution/dai-ma-sui-xiang-lu-dai-ni-gao-ding-zhan-y82r/

func main() {
    input := []int{1, 3, -1, -3, 5, 3, 6, 7}
    k := 3
    fmt.Println(maxSlidingWindow2(input, k))
}

type StrictQueue struct {
    Queue []int
}

func NewStrictQueue() *StrictQueue {
    return &StrictQueue{
        Queue: make([]int, 0),
    }
}

// 如果队列不为空且要移除的元素为队列中最大值,则将其弹出,否则不做任何操作,因为我们关心的是队列中的最大值
func (sq *StrictQueue) Pop(value int) {
    if !sq.IsEmpty() && sq.Queue[0] == value {
        sq.Queue = sq.Queue[1:]
    }
}

// 如果队列不为空且要添加的元素大于队列入口元素,则将队列入口元素弹出,直到添加的元素小于等于队列入口元素为止,以保证队列是严格单调递减的。
func (sq *StrictQueue) Push(value int) {
    for !sq.IsEmpty() && sq.Queue[sq.Size()-1] < value {
        sq.Queue = sq.Queue[:sq.Size()-1]
    }
    sq.Queue = append(sq.Queue, value)
}

// 返回队列中的第一个元素,此处是最大值
func (sq *StrictQueue) Peek() int {
    return sq.Queue[0]
}

func (sq *StrictQueue) IsEmpty() bool {
    return len(sq.Queue) == 0
}

func (sq *StrictQueue) Size() int {
    return len(sq.Queue)
}

// maxSlidingWindow 时间复杂度O(N), 空间复杂度O(N)
func maxSlidingWindow(nums []int, k int) []int {
    var res []int
    sq := NewStrictQueue()
    for i := 0; i < k; i++ { // 首先将前k个元素添加到队列中
        sq.Push(nums[i])
    }
    fmt.Println(sq.Queue)

    res = append(res, sq.Peek()) // 将前k个元素中的最大值添加到结果集合中

    for i := k; i < len(nums); i++ {
        sq.Pop(nums[i-k]) //        // 队列移除最前面元素
        //fmt.Println(sq.Queue)
        sq.Push(nums[i]) // 向队列添加新元素,也就是当前元素nums[i]
        //fmt.Println(sq.Queue)
        res = append(res, sq.Peek()) // 将当前队列中的最大值添加到结果集合中
    }
    return res
}


//==================  解法二 :单调队列,队列里存的是 index   ==================
// B站上古城算法,模板思路是:
// a:去尾操作:队尾元素出队列,如果队列有新元素待入队,需要从队尾开始,删除影响队列单调性的元素,维护队列的单调性。
// b: 去尾操作后,将新元素入列
// c: 删头操作:队头元素出队列,如果队列中总数超了窗口大小了,就要删除队头接着找其他的了,单调队列的队头就是窗口内的极值
// d: 取解操作:取出队头元素,就是窗口内的极值
// 1. index=0 入队列
// 2、num[index=1]>num[index=0],右出queue,即把index=0剔出,保证队列单调递减
// 3、index=1入队列
// 4、idex=2入队列
// 5、已经遍历到index=2,可以找一把最大值了,最大值是queue最左边index对应的数值
// 6、index=3入队列,此时队列里有[1 2 3]
// 7、左出queue,因为k=3,队列里已经有三个index了......
func maxSlidingWindow(nums []int, k int) []int {
    lenNum := len(nums)
    res := make([]int, 0)
    sq2 := NewStrictQueue()
    fmt.Println(sq2.Queue)

    for i := 0; i < lenNum; i++ {
        startWindow := i - k + 1
        for !sq2.IsEmpty() && i-sq2.peekFirst() >= k { // 左出queue,保证窗口为k-1
            sq2.pollFirst()
            fmt.Println(sq2.Queue)
        }

        for !sq2.IsEmpty() && nums[sq2.peekLast()] <= nums[i] { // 右出queue,保证递减队列
            sq2.pollLast()
            fmt.Println(sq2.Queue)

        }
        sq2.offerLast(i) // 进queue,此时queue.size==k
        fmt.Println(sq2.Queue)

        if startWindow >= 0 {
            res = append(res, nums[sq2.peekFirst()]) // 使用queue左边最大值
        }
    }
    return res
}

type StrictQueue struct {
    Queue []int
}

func NewStrictQueue() *StrictQueue {
    return &StrictQueue{
        Queue: make([]int, 0),
    }
}

// peekFirst()方法用于返回此双端队列表示的队列的第一个元素,但不删除该元素。
func (sq *StrictQueue) peekFirst() int {
    if !sq.IsEmpty() {
        return sq.Queue[0]
    }
    return -1
}

// peekLast()方法最后一个元素或结尾元素,但不会从列表中删除最后一个元素。
func (sq *StrictQueue) peekLast() int {
    if !sq.IsEmpty() {
        return sq.Queue[len(sq.Queue)-1]
    }
    return -1
}

// pollFirst 检索并删除此列表的第一个元素,如果此列表为空,则返回null。
func (sq *StrictQueue) pollFirst() {
    if !sq.IsEmpty() {
        sq.Queue = sq.Queue[1:]
    }
}

// pollLast()检索并删除此列表的最后一个元素,如果此列表为空,则返回null。
func (sq *StrictQueue) pollLast() {
    if !sq.IsEmpty() {
        sq.Queue = sq.Queue[:len(sq.Queue)-1]
    }
}

// offerLast用于在此Deque的末尾添加特定元素。
func (sq *StrictQueue) offerLast(value int) {
    sq.Queue = append(sq.Queue, value)
}

func (sq *StrictQueue) IsEmpty() bool {
    return len(sq.Queue) == 0
}

func (sq *StrictQueue) Size() int {
    return len(sq.Queue)
}

862. 和至少为 K 的最短子数组(困难)

给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。子数组 是数组中 连续 的一部分。

输入:nums = [1,2], k = 4
输出:-1

输入:nums = [2,-1,2], k = 3
输出:3

// 维护一个递增的queue.
// 先求出前缀和,排队,对于某个人来说,要找到前面比我矮K的人,而且我和他的位置要最近。
func shortestSubarray(nums []int, k int) int {
    len_nums := len(nums)
    prefixSum := make([]int, len_nums+1)
    prefixSum[0] = 0 // 固定模板,前缀和第0个元素初始化为0
    for i := 0; i < len_nums; i++ {
        prefixSum[i+1] = prefixSum[i] + nums[i]
    }

    squeue := NewStrictQueue()
    res := math.MaxInt32
    for i := 0; i < len_nums+1; i++ {
        for !squeue.IsEmpty() && prefixSum[i]-prefixSum[squeue.peekFirst()] >= k {

            res = min(res, i-squeue.peekFirst())
            //fmt.Println(squeue.Queue)
            //fmt.Println(squeue.peekFirst())

            squeue.pollFirst()
        }

        for !squeue.IsEmpty() && prefixSum[squeue.peekLast()] >= prefixSum[i] { //保证是递增队列
            squeue.pollLast()
            //fmt.Println(squeue.Queue)

        }
        squeue.offerLast(i)
        //fmt.Println(squeue.Queue)

    }
    
    if res <= len_nums {  // 最后这个没看懂
        return res
    }
    return -1
}

func min(a, b int) int {
    if a > b {
        return b
    }
    return a
}

type StrictQueue struct {
    Queue []int
}

func NewStrictQueue() *StrictQueue {
    return &StrictQueue{
        Queue: make([]int, 0),
    }
}

// peekFirst()方法用于返回此双端队列表示的队列的第一个元素,但不删除该元素。
func (sq *StrictQueue) peekFirst() int {
    if !sq.IsEmpty() {
        return sq.Queue[0]
    }
    return -1
}

// peekLast()方法最后一个元素或结尾元素,但不会从列表中删除最后一个元素。
func (sq *StrictQueue) peekLast() int {
    if !sq.IsEmpty() {
        return sq.Queue[len(sq.Queue)-1]
    }
    return -1
}

// pollFirst 检索并删除此列表的第一个元素,如果此列表为空,则返回null。
func (sq *StrictQueue) pollFirst() {
    if !sq.IsEmpty() {
        sq.Queue = sq.Queue[1:]
    }
}

// pollLast 检索并删除此列表的最后一个元素,如果此列表为空,则返回null。
func (sq *StrictQueue) pollLast() {
    if !sq.IsEmpty() {
        sq.Queue = sq.Queue[:len(sq.Queue)-1]
    }
}

// offerLast 用于在此Deque的末尾添加特定元素。
func (sq *StrictQueue) offerLast(value int) {
    sq.Queue = append(sq.Queue, value)
}

func (sq *StrictQueue) IsEmpty() bool {
    return len(sq.Queue) == 0
}

func (sq *StrictQueue) Size() int {
    return len(sq.Queue)
}

1425. 带限制的子序列和(困难)

1438. 绝对差不超过限制的最长连续子数组(中等)

1696. 跳跃游戏 VI(中等)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容