[LeetCode][239]【学习日记】滑动窗口最大值——O(n)单调队列

news2024/10/24 1:55:32

题目

239. 滑动窗口最大值

  • 难度:困难
  • 相关标签
  • 相关企业
  • 提示

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

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

示例 1:

输入: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

示例 2:

输入:nums = [1], k = 1 输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

解法选择

  • 这道题如果在滑动窗口中每次都进行排序找到最大值,那么复杂度就很高:对于窗口有 k 个的元素,遍历使用 O(k);对于长度为 n 的数组 nums,有 n-k+1 个窗口,所以复杂度为 O((n−k+1)k)=O(nk),肯定超时
  • 容易想到:如果使用 map 统计并自动排序,滑动窗口后,插入和删除的元素都会被自动排序,找到最大值的时间复杂度为常数,但是插入元素的时间复杂度为O(logn),如果 nums 递增,那么就 n 个元素都要插入,时间复杂度为 O(nlog⁡n)
  • 记录窗口中所有数据的排序并没有必要,因为在窗口中,当右侧元素比左侧的元素大,由于窗口是向右移动的,那么只要右侧元素还在窗口中,窗口中的的最大值就一定不可能是左侧那个元素,所以就可以将左侧那个元素放心地移除,所以根本不用记录左边那个元素——总而言之,只需要记录右侧比左侧大的元素

解法1:单调队列

单调队列原始版本

思考过程:


  • 首先,这道题的本质是:在数组中,有一个滑动窗口从左向右不断滑动,每次滑动窗口时,会删除最左边的元素并增加最右边的元素。

  • 思路:这个滑动过程让我想到了队列,即先进先出(FIFO)的数据结构。每次窗口删除或新增元素时,需要快速判断这些操作对窗口内的最大值是否产生影响。因此,可以设定一个队列,其中维护着一个从最左边元素开始逐渐增大的序列,例如:

    • 当窗口内有
      [14, 2, 27]
      
      时,队列会变为
      [14, 27]
      

    这样做的好处是,队列的尾部必然是窗口内的最大值,并且当删除最左边的元素时,可以迅速判断该元素是否影响到队列中的队尾(即最大值);增加元素时,只需判断其是否大于队尾即可。


class Solution {
public:
    vector<int> maxAltitude(vector<int>& heights, int limit) {
        queue<int> maxQ;
        vector<int> ans;
        if(heights.empty()) return ans;
        //先构建一个以窗口最左侧元素为队头,单调递增的队列
        for(int i=0; i<limit; ++i){
            if(maxQ.empty() || heights[i]>=maxQ.back()){
                maxQ.push(heights[i]);
            }
        }

        for(int i=limit; i<heights.size(); ++i){
            ans.push_back(maxQ.back());
            //保证队列里面始终有元素的情况下
            if(maxQ.front()==heights[i-limit]){
                maxQ.pop();//pop后队列内可能没有元素或者还有元素
                if(maxQ.empty()){
                    for(int j=i-limit+1; j<=i; ++j){//队列为空时,根据当前窗口重新构建单调递增序列
                        if(maxQ.empty() || heights[j]>=maxQ.back()){
                            maxQ.push(heights[j]);
                        }
                    }
                    continue;//如果重新构建了单调递增队列,则无需增加窗口最右侧的元素到队列中
                }
            }
            if(heights[i]>=maxQ.back()){//如果窗口最右侧元素比队列中最大的元素(队尾元素)还大,则添加到队尾
                maxQ.push(heights[i]);
            }
        }
        ans.push_back(maxQ.back());
        return ans;
    }
};

单调队列改进版本

  • 原始版本的单调队列就可以通过LCR183
  • 对比官方解法的单调队列的特点是,原始版本的单调队列是单调递增的,笔者原来的想法是这样就可以通过队列的结尾快速地访问到最大值,而官方的单调队列是将最大值放在队头的,要想快速的访问到最大值,每次插入新的最大值前都需要对队列中原有的值 pop 出来再插入,看起来比较麻烦
  • 但是原始版本的单调队列如果在滑动过程中遇到队列 pop 后为空,就需要根据当前窗口重新构建一个单调队列,复杂度将大大提高,也就是下面这一段代码:
for(int j=i-limit+1; j<=i; ++j){//队列为空时,根据当前窗口重新构建单调递增序列
                        if(maxQ.empty() || heights[j]>=maxQ.back()){
                            maxQ.push(heights[j]);
                        }
                    }
  • 这导致原始单调队列在239. 滑动窗口最大值这一题会遇到超时的问题
  • 但是原始版本的单调队列无法简单地修改为改进版本的单调队列,因为改进的队列存储的是 nums 的下标,这样的好处是相同大小但是不同位置处的数字因为有着不同的下标所以方便区分,在滑动窗口需要更新队列的时候,单纯以下面这行代码更新队列,会导致窗口中重复的最大值只在队列中记录一次
if(maxQ.front()==heights[i-limit]) maxQ.pop_front();
  • 解释:
  1. 窗口:[-7,-8,7,5] 队列:[7,5]
  2. while(!maxQ.empty() && heights[i]>=maxQ.back()) maxQ.pop_back();
    maxQ.push_back(heights[i]);
  3. 窗口:[-8,7,5,7] 队列:[7](此时开始出错,队列中应该有两个7)
  4. 由于直到第一个 7 移出窗口范围时,窗口内的最大值始终是 7,故队列中一直只有一个 7,在第一个 7 移出去后,有:窗口:[5,7,1,6] 队列:[6](队列中应该是7,而不是6)

在这里插入图片描述

官方单调队列

  • 官方的队列保持了队列始终非空,且单调递减,如果新添加的元素是最大的,那么队列清空后再将新增元素放入队列中;滑动窗口后,如果要删除的元素在队头,直接出队
  • 换而言之,官方单调队列只维护窗口中最大值右侧的单调递减队列,因为当右侧元素比左侧的元素大,由于窗口是向右移动的,那么只要右侧元素还在窗口中,窗口中的的最大值就一定不可能是左侧那个元素,所以就可以将左侧那个元素放心地移除,所以根本不用记录左边那个元素,所以队列为空的时候无需按照整个窗口重新构建队列!
  • 所以原始的单调队列解法的问题是,其维护的是窗口中最大值左侧的单调递增队列,在
    [1,2,3,4,4,4,3,2,1] 窗口大小为3 这种题中,当窗口移动到[3,2,1]就需要遍历整个窗口重新构建队列,当窗口很大就会很耗时
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        deque<int> q;
        for (int i = 0; i < k; ++i) {
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);
        }

        vector<int> ans = {nums[q.front()]};
        for (int i = k; i < n; ++i) {
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);
            while (q.front() <= i - k) {
                q.pop_front();
            }
            ans.push_back(nums[q.front()]);
        }
        return ans;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/sliding-window-maximum/solutions/543426/hua-dong-chuang-kou-zui-da-zhi-by-leetco-ki6m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解法2:大顶堆

大顶堆,C++中的优先队列,O(logn)插入元素后,最大值永远在队头O(1)
官方代码:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        priority_queue<pair<int, int>> q;
        for (int i = 0; i < k; ++i) {
            q.emplace(nums[i], i);
        }
        vector<int> ans = {q.top().first};
        for (int i = k; i < n; ++i) {
            q.emplace(nums[i], i);
            while (q.top().second <= i - k) {
                q.pop();
            }
            ans.push_back(q.top().first);
        }
        return ans;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/sliding-window-maximum/solutions/543426/hua-dong-chuang-kou-zui-da-zhi-by-leetco-ki6m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1498099.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

devc++8x8取模软件

这几天在搞arduino nano和单个max7219模块&#xff0c;涉及到16进制的取模&#xff0c;在网上转了一圈&#xff0c;没找到合适的取模软件&#xff0c;于是自己做了一个&#xff0c;试过&#xff0c;可以用&#xff0c;按esc退出并生成16进制的取模结果 源代码&#xff1a; #i…

Unity 动画(旧版-新版)

旧版 旧版-动画组件&#xff1a;Animation 窗口-动画 动画文件后缀: .anim 将制作后的动画拖动到Animation组件上 旧版的操作 using System.Collections; using System.Collections.Generic; using UnityEngine;public class c1 : MonoBehaviour {// Start is called before…

Latex公式太长换行标号

Latex中公式太长换行,且编号,可以采用align,不编号行公式用\nonumber,示例如下: \begin{align}\nonumber %第1行公式不编号&a+b+a+b+a+b+a+b+a+b+a+b+a+b+a\\&+c+d=m %第2行公式编号 \end{align}效果如下 原文件链接 公式不同命令的区别 \begin{align} 与 \…

信号处理--卷积残差网络实现单通道脑电的睡眠分期监测

目录 背景 亮点 环境配置 数据 方法 结果 代码获取 参考文献 背景 人类大约花三分之一的时间睡觉&#xff0c;这使得监视睡眠成为幸福感的组成部分。 在本文中&#xff0c;提出了用于端到端睡眠阶段的34层深残留的Convnet架构 亮点 使用深度1D CNN残差架构&#xff0…

高并发服务器模型

高并发服务器模型 1.高并发服务器模型--select2.高并发服务器模型--poll3.epoll模型3.1 epoll原理3.2epoll反应堆 1.高并发服务器模型–select 我们知道实现服务器的高并发&#xff0c;可以用多线程或多进程去实现。但还可以利用多路IO技术:select来实现&#xff0c;它可以同时…

【框架学习 | 第二篇】暴打MyBatis-Plus——MyBatis的升级版本

教程来源链接&#xff1a;https://www.quanxiaoha.com/mybatis-plus/mybatis-plus-tutorial.html 教程作者&#xff1a;犬小哈 文章目录 1.Mybatis Plus介绍1.1Mybatis和Mybatis Plus的区别是什么1.1.1什么是Mybatis?1.1.2区分Mybatis Plus和Mybatis 1.2Mybatis Plus特点1.3支…

【C语言】终の指针(前篇)

个人主页点这里~ 指针初阶点这里~ 指针初阶2.0点这里~ 指针进阶点这里~ 终の指针 一、回调函数二、qsort函数1、整形比较2、结构数据比较①结构体②-> 的使用③结构数据比较 一、回调函数 回调函数就是⼀个通过函数指针调用的函数。 把一个函数的指针作为参数传递给另一…

勾股定理的七种经典证明

据说勾股定理约有500种证明方法&#xff0c;下面介绍几种经典的证明方法。 一、切割重拼法。 顾名思义&#xff0c;就是将图形切割成其他形式的图形&#xff0c;然后通过拼图转换为另一种图形&#xff0c;这个过程中图形的面积是不变的。 “赵爽弦图”是这种方法的经典应用&…

Mysql案例之GROUP_CONCAT函数详解

Hello&#xff0c;大家好&#xff0c;我是灰小猿&#xff0c;一个超会写bug的程序员&#xff01; 今天这篇文章记录一个最近开发中遇到的mysql实战场景&#xff0c;觉得还挺典型的&#xff0c;就在此做一下记录。 先看一下举例场景&#xff1a; mysql中学生表与学科表通过关…

Linux设备模型(九) - bus/device/device_driver/class

一&#xff0c;设备驱动模型 1&#xff0c;概述 在前面写的驱动中&#xff0c;我们发现编写驱动有个固定的模式只有往里面套代码就可以了&#xff0c;它们之间的大致流程可以总结如下&#xff1a; 实现入口函数xxx_init()和卸载函数xxx_exit() 申请设备号 register_chrdev_r…

首发:鸿蒙面试真题分享【独此一份】

最早在23年华为秋季发布会中&#xff0c;就已经宣布了“纯血鸿蒙”。而目前鸿蒙处于星河版中&#xff0c;加速了各大互联网厂商的合作。目前已经有200参与鸿蒙的原生应用开发当中。对此各大招聘网站上的鸿蒙开发需求&#xff0c;每日都在增长中。 2024大厂面试真题 目前的鸿蒙…

OpenHarmony教程指南—ArkUI中组件、通用、动画、全局方法的集合

介绍 本示例为ArkUI中组件、通用、动画、全局方法的集合。 本示例使用 Tabs容器组件搭建整体应用框架&#xff0c;每个 TabContent内容视图 使用 div容器组件 嵌套布局&#xff0c;在每个 div 中使用 循环渲染 加载此分类下分类导航数据&#xff0c;底部导航菜单使用 TabCont…

LeetCode 2917.找出数组中的 K-or 值:基础位运算

【LetMeFly】2917.找出数组中的 K-or 值&#xff1a;基础位运算 力扣题目链接&#xff1a;https://leetcode.cn/problems/find-the-k-or-of-an-array/ 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 nums 中的 K-or 是一个满足以下条件的非负整数&#xff1a; 只有…

如何合理布局子图--确定MATLAB的subplot子图位置参数

确定MATLAB的subplot子图位置参数 目录 确定MATLAB的subplot子图位置参数摘要1. 问题描述2. 计算过程2.1 确定子图的大小和间距2.2 计算合适的figure大小2.3 计算每个子图的position数据 3. MATLAB代码实现3.1 MATLAB代码3.2 绘图结果 4. 总结 摘要 在MATLAB中&#xff0c;使用…

网络编程套接字(1)—网络编程基础

目录 一、为什么需要网络编程? 二、什么是网络编程 三、网络编程中的基本概念 1、发送端和接收端 2、请求和响应 3、客户端和服务端 四、常见的客户端服务端模型 1、一问一答模型 2、一问多答模型 3、多问一答模型 4、多问多答模型 一、为什么需要网络编程? 为什么…

(二十二)从零开始搭建k8s集群——高可用kubernates集群搭建上篇

前言 本节内容分为上、中、下三篇&#xff0c;上篇主要是关于搭建k8s的基础环境&#xff0c;包括服务器基本环境的配置&#xff08;网络、端口、主机名、防火墙、交换分区、文件句柄数等&#xff09;、docker环境部署安装配置、镜像源配置等。中篇会介绍k8s的核心组件安装、k8…

rk3568 恢复出厂设置横屏

author daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 daisy.skye_嵌入式,Linux,Qt-CSDN博客daisy.skye擅长嵌入式,Linux,Qt,等方面的知识https://blog.csdn.net/qq_40715266?typeblog 在使用rk3568开发过程&#xff0c;虽然显示的方向已经改成了横屏&#xff0c;但是恢…

4.1k star,官方出品的redis桌面管理工具——redislnsight

导航 令人抓狂的大key加载RedisInsight 简介RedisInsight的亮点GitHub 地址安装和使用RedisInsight 下载安装 使用RedisInsight redis数据库可视化直观的CLI&#xff08;Command-Line Interface&#xff09;日志分析和命令分析 结语参考 令人抓狂的大key加载 工欲善其事必先利…

JavaScript基础4之原型的原型继承、原型链和理解对象的数据属性、访问器属性

JavaScript基础 原型原型继承问题解决 原型链isPrototypeOf()Object.getPrototypeOf() 理解对象数据属性访问器属性 原型 原型继承 继承是面向对象编程的另一个特征&#xff0c;通过继承进一步提升代码封装的程度&#xff0c;JavaScript中大多是借助原型对象实现继承的特性。…

sudo command not found

文章目录 一句话Intro其他操作 一句话 sudo 某命令 改成 sudo -i 某命令 试试。 -i 会把当前用户的环境变量带过去&#xff0c;这样在sudo的时候&#xff0c;有更高的权限&#xff0c;有本用户的环境变量(下的程序命令)。 -i, --login run login shell as the target user; a …