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

题目

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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# Mel-Spectrogram 梅尔频谱

目录 介绍 Main features Philosophy of NWaves 效果 项目 代码 下载 C# Mel-Spectrogram 梅尔频谱 介绍 利用NWaves实现Mel-Spectrogram 梅尔频谱 NWaves github 地址&#xff1a;https://github.com/ar1st0crat/NWaves NWaves is a .NET DSP library with a lot …

SAP PP学习笔记 - 豆知识08 - 如何修改价格

正常的品目修改用MM02。 新建一个品目之后&#xff0c;啥都没干&#xff0c;现在想修改一下价格&#xff0c;发现MM02 修改不了了。 1&#xff0c;MR21 这里注意 转记日付 要和会计期间一致。 比如我这里的会计期间是 2024/03 有关会计期间&#xff0c;可以参照如下文章&am…

项目经理如何应对多系统对接的项目?

对于项目经理来说&#xff0c;处理系统对接&#xff08;API对接&#xff09;的需求是一项既复杂又关键的任务。这项任务涉及到确保不同的系统能够高效、安全地共享数据&#xff0c;从而实现流畅的业务流程和提高整体的系统性能。下面是一个详细的指南&#xff0c;旨在帮助产品经…

部署zabbix6.0.27 执行 make install 报错

CentOS7 部署 zabbix6.0.27 执行 make install 报错 报错信息 [rootlocalhost zabbix-6.0.27]# make install /usr/bin/ld: warning: libssl.so.3, needed by /usr/local/mysql/lib/libmysqlclient.so, not found (try using -rpath or -rpath-link) /usr/bin/ld: warning: l…

论文阅读:SDXL Improving Latent Diffusion Models for High-Resolution Image Synthesis

SDXL Improving Latent Diffusion Models for High-Resolution Image Synthesis 论文链接 代码链接 介绍 背景&#xff1a;Stable Diffusion在合成高分辨率图片方面表现出色&#xff0c;但是仍然需要提高本文提出了SD XL&#xff0c;使用了更大的UNet网络&#xff0c;以及增…

Java定时调度范式定时操作

在 Java 中&#xff0c;我们可以使用各种方法来执行定时操作。这些操作包括执行任务、调度任务、执行重复任务等。下面将介绍几种常见的 Java 定时调度范式。 1. Timer 和 TimerTask Java 提供了 Timer 和 TimerTask 类&#xff0c;用于执行定时任务。 示例代码&#xff1a;…

【JavaEE初阶】 JVM简介

文章目录 &#x1f38d;前言&#x1f343;JVM发展史&#x1f6a9;Sun Classic VM&#x1f6a9;Exact VM&#x1f6a9;HotSpot VM&#x1f6a9;JRockit&#x1f6a9;J9 JVM&#x1f6a9;Taobao JVM&#xff08;国产研发&#xff09; &#x1f340;JVM 运行流程⭕总结 &#x1f3…

【Datawhale组队学习:Sora原理与技术实战】

Transformersdiffusion技术背景简介 Transformers diffusion背景 近期大火的OpenAI推出的Sora模型&#xff0c;其核心技术点之一&#xff0c;是将视觉数据转化为Patch的统一表示形式&#xff0c;并通过Transformers技术和扩散模型结合&#xff0c;展现了卓越的scale特性。 被…

成功实施自动化测试的优点

随着技术的发展&#xff0c;保证应用程序的质量变得越来越具有挑战性。由于敏捷开发和成本因素&#xff0c;导致了发现问题窗口时间有限&#xff0c;因此测试经常会忽略某些应该关注的地方。 测试工程师应该在发布产品之前发现其中存在的问题&#xff0c;但是任何软件都不可能…

SpringBoot项目如何添加全局接口上下文

1. 定义Spring Boot应用的路由 首先&#xff0c;确保您的Spring Boot应用有一个统一的路由前缀。例如&#xff0c;可以在application.properties或application.yml配置文件中使用server.servlet.context-path属性来定义所有请求的基础路径。 # application.properties server…

Ansible 基础入门

2&#xff09;Ansible 介绍 Ansible 基本概念 Ansible 是一种自动化运维工具&#xff0c;基于 Paramiko 开发的&#xff0c;并且基于模块化工作&#xff0c;Ansible 是一种集成 IT 系统的配置管理、应用部署、执行特定任务的开源平台&#xff0c;它是基于 Python 语言&#xf…

sudo command not found

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

I’m stuck!(CCF201312-5)解析(java实现)

代码 package test_201312;import java.util.Scanner;/** 201312-5 试题名称&#xff1a; I’m stuck! 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 256.0MB 问题描述&#xff1a; 问题描述给定一个R行C列的地图&#xff0c;地图的每一个方格可能是#, , -, |, ., S, T七…

JS使用方式

JS是解释性语言&#xff0c;所以不需要搭建类似C#/Java之类的开发运行环境&#xff0c;因为他们是编译型语言。JS一般运行在浏览器中或者node环境中&#xff0c;这里都是JS引擎的功劳。 node环境使用 推荐使用nvm管理node版本&#xff0c;nrm管理代理地址。 安装node&#xf…

腾讯云服务器和阿里云服务器哪家更优惠?2024价格对比

2024年阿里云服务器和腾讯云服务器价格战已经打响&#xff0c;阿里云服务器优惠61元一年起&#xff0c;腾讯云服务器61元一年&#xff0c;2核2G3M、2核4G、4核8G、4核16G、8核16G、16核32G、16核64G等配置价格对比&#xff0c;阿腾云atengyun.com整理阿里云和腾讯云服务器详细配…

【蓝桥杯基础算法】dfs(上)组合数,全排列

刚接触算法&#xff0c;有没有被递归又循环的dfs吓到&#xff1f;没关系&#xff0c;几个例题就可以彻底掌握&#xff01; 1.全排列 1-n的全排列,如输入3&#xff0c;按顺序对1-3进行排列 //枚举 #include<iostream> #include<algorithm> #include<cstring>…

【Linux基础(二)】进程管理

学习分享 1、程序和进程1.1、程序1.2、进程和进程ID 2、Linux下的进程结构3、init进程4、获取进程标识5、fork系统调用5.1、fork函数实例分析 6、进程的特性7、在Linux下进程指令7.1、终止进程指令7.2、查看进程指令&#xff1a;7.3、以树状图列出进程 8、多进程运行异常情况8.…

【Spring云原生系列】Spring Cloud Stream:消息驱动架构(MDA)解析,实现异步处理与解耦合!

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

2024年抖店新商家自学全套教程,完整版店铺操作流程,如下!

我是王路飞。 想做一个项目的话&#xff0c;就要先了解其完整的流程是怎样的。 做抖店也不例外&#xff0c;没开店的就先了解下抖店的基本信息和大概运营流程&#xff1b;开过店的就先让自己入门并把流程跑通&#xff0c;如此才有承接后续渠道和资源的能力。 今天这篇文章专…

计算机网络:应用层知识点汇总

文章目录 一、网络应用模型二、域名系统&#xff08;DNS&#xff09;三、文本传输协议&#xff08;FTP&#xff09;四、电子邮件五、万维网和HTTP协议 一、网络应用模型 p2p也就是对等模型 二、域名系统&#xff08;DNS&#xff09; 我们知道&#xff0c;随着人们建立一个网站…