LeetCode - 239 滑动窗口最大值

news2024/11/18 3:40:12

目录

题目来源

题目描述

示例

提示

题目解析

算法源码


题目来源

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

 

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

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

相关文章

AutoCAD介绍——带你了解最强的CAD软件

AutoCAD介绍——带你了解最强的CAD软件 什么是AutoCAD应用领域功能特点版本发展总结 什么是AutoCAD Autodesk的AutoCAD是一款世界著名的CAD软件&#xff0c;其全称为“Auto Computer-Aided Design”&#xff0c;是一种计算机辅助设计工具&#xff0c;用于帮助用户创建和编辑二…

k8s之审计日志

一、为什么要有审计 Kube-Apiserver 的负载突然变高&#xff0c;大量访问失败&#xff0c;集群中到底发生了什么&#xff1f; 当集群发生问题时&#xff0c;这是Metric一般会失效&#xff0c;为了排查以上问题&#xff0c;k8s 提供了两种原生的日志形式——审计&#xff08;A…

PID整定二:基于Ziegler-Nichols的频域响应

PID整定二&#xff1a;基于Ziegler-Nichols的频域响应 1参考2连续Ziegler-Nichols方法的PID整定2.1整定方法2.2仿真示例 1参考 1.1根轨迹图的绘制及分析 1.2计算机控制技术01-3.4离散系统的根轨迹分析法 1.3PID控制算法学习笔记 2连续Ziegler-Nichols方法的PID整定 2.1整定…

2023软考中级《软件设计师》(备考冲刺版) | 知识产权与标准化

1.知识产权 1.1 保护对象和范围 1.2 保护期限 注意&#xff1a;注册商标续注可延长 1.3 知识产权人确定 1.3.1 职务作品&#xff08;区分个人和公司单位&#xff1a;大部分都是公司单位&#xff09; 1.3.2 委托开发、合作开发与同时申请 1.4 侵权判定 1.第二十五条对下列各…

操作系统——线程调度

0.关注博主有更多知识 操作系统入门知识合集 目录 6.1线程调度概念 思考题&#xff1a; 6.2典型调度算法 思考题&#xff1a; 6.3Linux线程调度 6.1线程调度概念 在第四章曾经介绍过&#xff0c;线程是操作系统调度的基本单位&#xff0c;那么本篇就不再以进程的视角去…

【操作系统OS】学习笔记第二章 进程与线程(下)【哈工大李治军老师】

基于本人观看学习 哈工大李治军老师主讲的操作系统课程 所做的笔记&#xff0c;仅进行交流分享。 特此鸣谢李治军老师&#xff0c;操作系统的神作&#xff01; 如果本篇笔记帮助到了你&#xff0c;还请点赞 关注 支持一下 ♡>&#x16966;<)!! 主页专栏有更多&#xff0…

基于rke部署的k8s集群如何配置kube-proxy工作在ipvs模式

kube-proxy默认工作在iptables模式下&#xff0c;在集群配置文件cluster.yml中添加如下配置项即可开启ipvs模式。然后执行 rke up 命令使配置生效。

软件测试的ABC

文章目录 一. 衡量软件测试结果的依据—需求1. 什么是需求2. 案例 - 平台支持邮箱注册3. 从测试人员角度看需求 二. 测试用例1. 测试用例的概念2. 案例3. 为什么要有测试用例 三. 软件错误 (BUG)1. 什么是bug2. 如何描述一个bug3. bug的级别4. bug的生命周期5. 如果因为bug和开…

Java面试题总结 | Java面试题总结8- Redis模块(持续更新)

Redis 文章目录 Redisredis的线程模型Redis的Mysql的区别Redis和传统的关系型数据库有什么不同&#xff1f;Redis常见的数据结构zset数据结构Redis中rehash过程redis为什么不考虑线程安全的问题呢Redis单线程为什么还能这么快&#xff1f;为什么Redis是单线程的&#xff1f;red…

【SpringMVC】| SpringMVC执行流程原理 | 常用注解 剥析

MVC目录 一. &#x1f981; MVC模型二. &#x1f981; SpringMVC1. SpringMVC执行流程&#xff08;重点&#xff09;Ⅰ. SpringMVC四大组件Ⅱ. 执行流程 2. RequestMapping3. RequestParam4. ReuqestHeader & CookieValue5. RESTful风格支持Ⅰ. 传统 vs restfulⅡ. PathVar…

5. Docker——先快速入门

本章讲解知识点 Docker 是什么为什么要使用 DockerDocker 引擎Docker 常用命令Docker 生命周期详解虽然我们上一章教大家搭建好了 Kubernetes + Docker 环境,但是我们本章是先从 Docker 讲起,为后面学习 Kubernetes 打好基础。 1. Docker 是什么 Docker 是一种开源的容器化…

spring5源码篇(9)——mybatis-spring整合原理

spring-framework 版本&#xff1a;v5.3.19 spring和mybatis的整合无非主要就是以下几个方面&#xff1a; 1、SqlSessionFactory怎么注入&#xff1f; 2、Mapper代理怎么注入&#xff1f; 3、为什么要接管mybatis事务&#xff1f; 文章目录 一、SqlSessionFactory怎么注入SqlSe…

离散数学_九章:关系(4)

9.4 关系的闭包 1、闭包(closure)的定义2、不同类型的闭包1. 自反闭包(reflexive closure)2. 对称闭包(symmetric closure)3. 传递闭包(transitive closure) 3、闭包的几个定理定理1定理2定理3 - R1∪R2定理4定理5&#x1f4d8;例题&#xff1a; 4、有向图中的路径5、传递闭包1…

2000-2019年30省研发资本存量(含计算过程和原始数据)

2000-2019年30省份研发资本存量&#xff08;含计算过程和原始数据&#xff09;/2000-2019年30个省市R&D资本存量或研发资本存量数据 1、时间&#xff1a;2000-2019年 2、范围&#xff1a;包括30个省市不含西藏 3、指标&#xff1a;省研发资本存量 4、参考文献&#xff…

家用 NAS 服务器搭建 | 网络 | DNS域名解析

1、前言 使用NAS&#xff0c;一般除了在家里通过局域网访问&#xff0c;还会有外网访问的需求&#xff0c;即在外面通过移动网络或者其他网络访问家中的NAS。 正常情况下在外面是没有办法访问家庭网络的&#xff0c;甚至是nas&#xff0c;因为nas获取的是局域网IP&#xff0c;…

【Python】flask

一、Flask教程 Flask是一个免费的web框架&#xff0c;也是一个年轻、充满活力的小型框架&#xff0c;开发文档齐 全&#xff0c;社区活跃度高&#xff0c;有着众多支持者。 Flask的设计目标是实现一个WSGI的微框架&#xff0c; 其核心代码十分简单。 Flask框架在中小型企业中的…

pwn的kali64虚拟机环境搭建记录

自己记着备用&#xff0c;pwn的环境和工具 虚拟机&#xff1a;VMware Workstation Pro Linux版本&#xff1a;kali64 总参考&#xff1a; pwn 环境搭建&#xff08;wsl/vmware&#xff09; pwn入门之环境搭建 目前就装这些&#xff0c;以后改了再更新&#xff08;但愿 安装ka…

第 4 章 HBase 进阶

第 4 章 HBase 进阶 4.1 Master 架构1&#xff09;Meta 表格介绍&#xff1a;&#xff08;警告&#xff1a;不要去改这个表&#xff09; 4.2 RegionServer 架构1&#xff09;MemStore2&#xff09;WAL&#xff08;预写日志&#xff09;3&#xff09;BlockCache 4.3 写流程2&…

使用kubeadm搭建生产环境的多master节点k8s高可用集群

环境centos 7.9 目录 1.对安装 k8s 的节点进行初始化配置 2 通过 keepalivednginx 实现 k8s apiserver 节点高可用 3、kubeadm 初始化 k8s 集群 4.扩容 k8s 控制节点&#xff0c;把 xuegod62 加入到 k8s 集群 5、扩容 k8s 控制节点&#xff0c;把 xuegod64 加入到 k8s 集群…

06_Uboot顶层Makefile分析_前期所做内容

目录 U-Boot顶层Makefile分析 版本号 MAKEFLAGS变量 命令输出 静默输出 设置编译结果输出目录 代码检查 模块编译 获取主机架构和系统 设置目标架构、交叉编译器和配置文件 调用scripts/Kbuild.include 交叉编译工具变量设置 导出其他变量 U-Boot顶层Makefile分析…