LeetCode - 239 滑动窗口最大值

目录

题目来源

题目描述

示例

提示

题目解析

算法源码


题目来源

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

题目描述

给你一个整数数组 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]
说明

提示

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

题目解析

本题最简单的思路其实就是定义一个长度为k的滑窗,然后每次求滑窗范围内最大值,但是这样的算法的时间复杂度为O((n - k + 1) * k),因此算法的性能比较差。

本题最佳解题思路是:单调队列。

单调队列在此处是用于维护滑窗的最大值的。

如下图是用例1的滑窗运动过程,和单调队列的变化

 

滑窗运动过程分为两块:

  • 初始滑窗的形成过程(即只有新增尾部元素的过程)
  • 滑窗的右移过程(即失去一个头部(绿色)元素,新增一个尾部元素)

我们假设滑窗的尾巴元素是tail,而新加入滑窗的元素是new,那么为了维护单调队列的单调性,本题是单调递减,只要滑窗的tail < new,那么就必须将tail出队,然后继续比较滑窗的新tail和new,直到滑窗的tail >= new了,或者滑窗为空了,此时将new加入到滑窗尾部。

上面逻辑对应单调队列的尾删操作、以及尾增操作。

另外,单调队列还有一个非常重要的头删操作。比如上图中如下两个过程

新滑窗失去了3元素,新增了5元素,那么其实此时单调队列[3, -1, -3]按顺序需要做如下两件事:

  • 先删除队列的头元素3,此时单调队列变为[-1, -3]
  • 然后再加入5到队列,此时单调队列变为[-1, -3, 5],为了维护单调性,因此依次尾删-3、-1,最后单调队列就只有[5]

从这个过程,我们发现单调队列中3元素,并不是为了维护单调性而被尾删的,而是被头删的。

为什么呢?

从上图两个黄色的滑窗,我们可以发现,滑窗移动过程中,失去了3元素,因此新滑窗需要删掉3元素。

这里我们可以对比下面过程来分析

上图中新滑窗失去了1元素,但是单调队列[3, -1]却没有执行头删,这是因为单调队列的头部元素3还在新滑窗中,因此不需要头删。

因此,只有新滑窗失去的元素 == 单调队列的头部元素 时,我们才需要进行单调队列的头删操作。 

Java算法源码

class Solution {
  public int[] maxSlidingWindow(int[] nums, int k) {
    // queue 是单调队列
    LinkedList<Integer> queue = new LinkedList<>();

    // ans 记录题解,一共有nums.length - k + 1滑窗
    int[] ans = new int[nums.length - k + 1];
    // j 记录当前滑窗的序号
    int j = 0;

    // 初始滑窗
    for (int i = 0; i < k; i++) {
      while (queue.size() > 0 && queue.getLast() < nums[i]) {
        queue.removeLast();
      }
      queue.add(nums[i]);
    }
    ans[j++] = queue.getFirst();

    // 后续滑窗
    for (int i = k; i < nums.length; i++) {
      // nums[i-k] 是滑窗失去的元素
      if (nums[i - k] == queue.getFirst()) {
        queue.removeFirst(); // 单调队列头删
      }

      // nums[i] 是滑窗新增的元素
      while (queue.size() > 0 && queue.getLast() < nums[i]) {
        queue.removeLast(); // 单调队列尾删
      }

      queue.add(nums[i]); // 单调队列尾增
      ans[j++] = queue.getFirst();
    }

    return ans;
  }
}

 

JavaScript算法源码

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function(nums, k) {
    const queue = [];
    const ans = [];

    for(let i=0; i<k; i++) {
        while(queue.length && queue.at(-1) < nums[i]) {
            queue.pop();
        }
        queue.push(nums[i]);
    }
    ans.push(queue[0]);

    for(let i=k; i<nums.length; i++) {
        if(nums[i-k] == queue[0]) {
            queue.shift();
        }

        while(queue.length && queue.at(-1) < nums[i]) {
            queue.pop();
        }
        queue.push(nums[i]);
        ans.push(queue[0]);
    }
    return ans;
};

上面JS使用的数组模拟的双端队列,因此shift()操作的性能非常差,下面代码中,我模拟了一个双端队列MyQueue,包含头部出队shift,尾部出队pop,尾部入队push,以及获取双端队列头部first和尾部值last。

 

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function(nums, k) {
    const queue = new MyQueue();
    const ans = [];

    for(let i=0; i<k; i++) {
        while(queue.length && queue.last() < nums[i]) {
            queue.pop();
        }
        queue.push(nums[i]);
    }
    ans.push(queue.first());

    for(let i=k; i<nums.length; i++) {
        if(nums[i-k] == queue.first()) {
            queue.shift();
        }

        while(queue.length && queue.last() < nums[i]) {
            queue.pop();
        }
        queue.push(nums[i]);
        ans.push(queue.first());
    }
    return ans;
};

class MyQueue{
    constructor() {
        this.head = null;
        this.tail = null;
        this.length = 0;
    }

    push(val) {
        const node = new Node(val);

        if(this.length == 0) {
            this.head = node;
            this.tail = node;
        } else {
            this.tail.next = node
            node.prev = this.tail
            this.tail = node
        }

        this.length += 1;
    }

    pop() {
        if(this.length > 0) {
            this.tail = this.tail.prev;
            if(this.tail) this.tail.next = null;
            this.length -= 1;
        }
    }

    shift() {
        if(this.length > 0) {
            this.head = this.head.next;
            if(this.head) this.head.prev = null;
            this.length -= 1;
        }
    }

    last() {
        return this.tail.val;
    }

    first() {
        return this.head.val;
    }
}

class Node {
    constructor(val) {
        this.val = val;
        this.prev = null;
        this.next = null;
    }
}

 

Python算法源码

from collections import deque


class Solution(object):
    def maxSlidingWindow(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """

        # dq 是单调队列
        dq = deque()
        # ans 记录题解,一共有nums.length - k + 1滑窗
        ans = []

        # 初始滑窗
        for i in range(k):
            while len(dq) > 0 and dq[-1] < nums[i]:
                dq.pop()
            dq.append(nums[i])
        ans.append(dq[0])

        # 后续滑窗
        for i in range(k, len(nums)):
            # nums[i-k] 是滑窗失去的元素
            if nums[i - k] == dq[0]:
                dq.popleft()  # 单调队列头删

            # nums[i] 是滑窗新增的元素
            while len(dq) > 0 and dq[-1] < nums[i]:
                dq.pop()  # 单调队列尾删

            dq.append(nums[i])  # 单调队列尾增
            ans.append(dq[0])

        return ans

 

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

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

相关文章

springboot+vue前后端分离项目打包成jar包及运行

将 Spring Boot 和 Vue.js 项目打包成 jar 包需要按照以下步骤操作&#xff1a; 在项目的根目录中&#xff0c;使用命令行进入 Vue.js 项目的根目录&#xff0c;然后运行以下命令&#xff1a; npm run build这个命令将会构建 Vue.js 项目&#xff0c;并在项目的 dist 目录中生…

鸿蒙Hi3861学习八-Huawei LiteOS(事件标记)

一、简介 事件是一种实现任务间通信的机制&#xff0c;可用于实现任务间的同步。但事件通信只能是事件类型的通信&#xff0c;无数据传输。一个任务可以等待多个事件的发生&#xff1a;可以是任意一个事件发生时唤醒任务进行事件处理&#xff1b;也可以是几个事件都发生后才唤醒…

华为网络设备+WinRadius 实现用户统一管理设备

一、直接贴配置 ###配置VTY用户界面所支持的协议、验证方式 user-interface vty 0 4 protocol inbound telnet authentication-mode aaa quit ###配置RADIUS认证 ###&#xff08;1&#xff09;配置RADIUS服务器模板&#xff0c;指定服务器的IP地址与端口号、共享密钥 radius-s…

Unity - Render Doc - 解决 Waiting For Debugger 导致连接不了 APP 的问题

环境 Unity : 2020.3.37f1 Pipeline : BRP RDC : 1.26 问题 平常有一些公司内的游戏发布在移动端运行会有各种异常&#xff0c;但是 unity editor (android opengl es / dx) 下正常 如果没有真机抓帧分析&#xff0c;是搞不定的 然后 RenderDoc 在抓发布出来的调试包也抓不…

漫画 | Linux之父:财务自由以后,我失眠了!

前言&#xff1a;今年是Linux诞生的30周年&#xff01; 1991年的8月&#xff0c; Linus在新闻组中公布了他正在开发的一个免费的操作系统&#xff0c;这也是以后风靡世界的Linux操作系统的雏形。 今天翻到这篇漫画&#xff0c;看到Linux的诞生过程&#xff0c;很是感慨&#x…

SuperMap GIS基础产品云GIS FAQ集锦(2)

SuperMap GIS基础产品云GIS FAQ集锦&#xff08;2&#xff09; 【iManager】云套件ispeco-dashboard-api的日志等级只有到info&#xff0c;如何设置才能查看到debug级别的日志&#xff1f; 【解决方案】可以在ispeco-dashboard-api的deployment中添加以下环境变量&#xff0c;…

vue框架快速入门

vue 1、第一个Vue程序1.1、什么是Vue程序1.2、为什么要使用MVVM1.3、Vue1.4、第一个vue程序 2、基础语法2.1、v-bind2.2、v-if&#xff0c; v-else2.3、v-for2.4、v-on 3、Vue表单双绑、组件3.1、什么是双向数据绑定3.2、在表单中使用双向数据绑定3.3、什么是组件 4、Axios异步…

PyQt5 基础篇(一)-- 安装与环境配置

1 PyQt5 图形界面开发工具 Qt 库是跨平台的 C 库的集合&#xff0c;是最强大的 GUI 库之一&#xff0c;可以实现高级 API 来访问桌面和移动系统的各种服务。PyQt5 是一套 Python 绑定 Digia QT5 应用的框架。PyQt5 实现了一个 Python模块集&#xff0c;有 620 个类&#xff0c;…

从0学会Spring框架

文章目录 1. 对Spring的理解2. Spring IoC3. DI4. 如何创建一个Spring项目4.1 创建一个Maven项目4.2 添加Spring框架支持4.3 添加启动类 5. 存储Bean对象5.1 添加配置文件5.2 创建Bean对象5.3 注册Bean 6. 获取并使用Bean对象7. 更简单存储Bean对象的方式7.1 前置工作7.2 添加存…

基于javaweb的学生就业管理系统

一、简介 学生基业管理系统有三个角色&#xff1a;管理员、企业、学生 对学生信息管理、企业信息管理、求职信息管理 后端架构&#xff1a;spring springmvc mybatis 前端架构&#xff1a;jsp layui 系统环境&#xff1a;jdk1.8 | maven | mysql 二、主要功能 1. 登录…

一个集团企业,如何从0到1构建信息化系统?

当今时代&#xff0c;信息技术已经成为企业发展不可或缺的一部分&#xff0c;特别是对于一个大型集团公司来说&#xff0c;如何构建一个高效的信息化系统对于其业务发展至关重要。 我们想要构建一个优质高效的信息化系统&#xff0c;首先需要了解现在大的趋势是怎样的。 目前…

看我如何通过帮助服务台轻松黑掉数百家公司

导语&#xff1a;几个月前&#xff0c;我发现黑客可以利用一个漏洞访问目标公司的内部通信。 这个漏洞只需要点击几下&#xff0c;就可以访问企业内部网络、 Twitter等社交媒体账户&#xff0c;以及最常见的Yammer和Slack团队。 更新: The Next Web 写了一篇我发现的这个漏洞的…

【Java笔试强训 28】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;猴子分桃…

二分法相关使用

文章目录 1. 在一个有序数组中,找某个数是否存在2. 在一个有序数组中,找大于等于某个数最左侧的位置3. 在一个有序数组中, 找小于等于某个数最右侧的位置4. 局部最大值问题 1. 在一个有序数组中,找某个数是否存在 在线OJ&#xff1a;704. 二分查找 有序数组下的二分思路如下:…

几种常见时间复杂度实例分析

多项式量级 常量阶 O(1) 对数阶 O(logn) 线性阶 O(n) 线性对数阶 O(nlogn) 平方阶O(n2 ),立方阶O(n3 )...k次方阶O(nk) 非多项式量级&#xff08;NP&#xff08;Non-Deterministic Polynomial&#xff0c;非确定多项式&#xff09;问题&#xff09; 指数阶O(2n) 阶乘阶…

Android WebRtc+SRS/ZLM视频通话(1):虚拟机安装Ubuntu

Android WebRtcSRS/ZLM视频通话&#xff08;1&#xff09;&#xff1a;虚拟机安装Ubuntu 来自奔三人员的焦虑日志 秉着没事找事的原则&#xff0c;这里直接从服务器安装开始说起&#xff0c;也当记录自己这一路以来的愚昧之举&#xff0c;由于没有物理服务器&#xff0c;这里以…

MySQL 精选 35 道面试题大厂稳了(含答案)

MySQL 精选 35 道面试题 1.说一下 MySQL 执行一条查询语句的内部执行过程&#xff1f;2.MySQL 查询缓存有什么优缺点&#xff1f;3.MySQL 的常用引擎都有哪些&#xff1f;4.常用的存储引擎 InnoDB 和 MyISAM 有什么区别&#xff1f;5.什么叫回表查询&#xff1f;6.如果把一个 I…

95后阿里P7晒出工资单:狠补了这个,真香···

最近一哥们跟我聊天装逼&#xff0c;说他最近从阿里跳槽了&#xff0c;我问他跳出来拿了多少&#xff1f;哥们表示很得意&#xff0c;说跳槽到新公司一个月后发了工资&#xff0c;月入5万多&#xff0c;表示很满足&#xff01;这样的高薪资着实让人羡慕&#xff0c;我猜这是税后…

TCP的粘包和拆包

UDP有数据边界&#xff0c;TCP是没有数据边界&#xff0c;是流协议。如何拆包&#xff0c;就要靠应用层来处理。 四层网络模型&#xff0c;消息在进入每一层时都会多加一个报头。mac头部记录的是硬件的唯一地址&#xff0c;IP头记录的是从哪来和到哪去&#xff0c;传输层头记录…

优化问题的拉格朗日Lagrange对偶法原理

首先我们定义一般形式的求解x的优化问题&#xff1a; 表示优化的目标函数&#xff0c;上述为最小优化&#xff0c;实际上最大优化可以改写为的形式表示第i个不等式约束表示等式约束 1. Lagrange对偶问题 上述优化问题的拉格朗日Lagrange对偶法求解&#xff0c;是将上述带约束…