【算法与数据结构】单调队列

news2025/2/23 12:03:42

目录

单调队列

使用单调队列维护滑动窗口

具体过程:

代码实现: 

复杂度分析:

使用单调队列优化动态规划

例题 


单调队列

单调队列(deque)是一种特殊的队列,队列中的元素始终严格递增或者递减排列。这样就可以保证队头元素始终是最大值或者最小值。

【问题引入】

有一个数组,长度为n,给你一个区间[left,right],求出该区间内的最小值或者最大值。

我们如果进行普通的遍历,最坏的情况下时间复杂度是O(N),遍历整个数组。

而我们如果用单调队列来维护这段区间,始终保持队列的单调性,就可以在O(1)的时间内找到该区间的最大值或者最小值,就是队头元素

【结论】

所以单调队列的核心用途是高效维护动态窗口的极值(最大值或最小值)。

那么对于一些动态规划,如转移方程需要进行一段区间的最值计算,可以使用单调队列优化。


使用单调队列维护滑动窗口

题目链接:239. 滑动窗口最大值 - 力扣(LeetCode)

当窗口形成后,我们需要找到这段窗口中的最大值,暴力的方法就是对这段区间进行遍历,每个元素进行比较,选出最大值。这样做时间复杂度为O(N^2),效率太低。

使用单调队列优化:单调队列维护该窗口,队头元素为最大元素。时间复杂度为O(N)。

具体过程:

  • 创建一个单调对列,维护该队列的递减性,以保证对头元素为窗口中的最小值
  • 对于该单调队列,存放元素的下标,按值递减排列。新来一个元素(也就是滑动窗口右移一步),需要判断对头元素是否还在窗口内,所以记录下标,便于判断对头元素是否还在窗口中,如果不在,删除对头元素。
  • 新来一个元素(也就是滑动窗口右移一步),每次都需要比较队尾元素与当前元素的大小,我们维护的是递减队列,如果队尾元素大于当前元素,则将当前元素的下标直接加入队列;如果队尾元素小于当前元素,则将队尾元素删除,直到队尾元素大于当前元素,再将当前元素下标加入队列,保持队列的递减性。
  • 窗口形成后,统计结果即可。队头元素就是最大元素。

代码实现: 

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        //双端队列 存储数组索引
        deque<int> dq;

        vector<int> res;
        for(int i=0;i<nums.size();i++)
        {
            //移除超出范围的队首元素
            while(!dq.empty()&&dq.front()<=i-k)
            dq.pop_front();

            //维护队列递减性(从队头到队尾),移除所有小于当前元素的队尾元素 
            while(!dq.empty()&&nums[dq.back()]<nums[i])
            dq.pop_back();

            //当前元素入队列
            dq.push_back(i);

            //窗口形成后,统计结果,队头元素就是最大元素
            if(i>=k-1)
            res.push_back(nums[dq.front()]);
        }
        return res;
    }
};

 上面的写法是使用库中的deque,还可以使用数组来模拟:


#include<iostream>
using namespace std;
const int N = 1e6 + 5;

int a[N], q[N];
int n, k;

int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];

    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i++)
    {
        while (hh <= tt && i - k >= q[hh]) hh++;//处理队首
        while (hh <= tt && a[q[tt]] <= a[i]) tt--;//处理队尾

        q[++tt] = i;//队尾元素加入
        if (i >= k) cout << a[q[hh]] << " ";//输出队首元素
    }
    cout << endl;
    return 0;
}

复杂度分析:

每个元素最多入队和出队一次,时间复杂度为O(N),队列最多存储k个元素,时间复杂度为O(K)。 


使用单调队列优化动态规划

在动态规划中,当状态转移需要在一个窗口查找极值时,可以使用单调队列优化时间复杂度。

题目链接:P5858 「SWTR-3」Golden Sword - 洛谷

状态表示:dp[i][j]表示加入第i个材料时, 锅内的材料个数为j(包括此时加入的),此时的耐久度的最大值。

状态转移方程的分析:加入第i个材料后的最大耐久度,等于加入第i-1个材料时最大的耐久度,再加上自身的耐久度,也就是再加上a[i]*j。

状态转移方程:dp[i][j]=max(dp[i][j],dp[i-1][k]+j*a[i]),j-1<=k<=min(w,j-1+s)。

正常使用动态规划:

第一层循环遍历i。

第二层循环遍历j,填写dp[i][j]。

但是由于有求[j-1,min(w,j-1+s)]最大值的操作,所以还需一层循环求最大值。

共三层循环,时间复杂度太大,需要进行优化。

#include <iostream>
using namespace std;
long long n, m, s;
long long a[5505];
long long dp[5505][5505];

int main()
{
	cin >> n >> w >> s;
	for (long long i = 1; i <= n; i++)
		cin >> a[i];
	for (long long i = 0; i <= n; i++)
		for (long long j = 0; j <= w; j++)
			dp[i][j] = -1e18;
	dp[0][0] = 0;
	//dp[i][j]选第i个材料时,此时正好j个材料的最大耐久度
	for (long long i = 1; i <= n; i++)
		for (long long j = 1; j <= w; j++)
			for (long long k = j - 1; k <= min(w, s + j - 1); k++)
				dp[i][j] = max(dp[i][j], dp[i-1][k] + a[i] * j);

	long long ans = -1e18;
	for (int i = 0; i <= w; i++)
		ans = max(ans, dp[n][i]);

	cout << ans << endl;

	return 0;
}

使用单调队列优化后

在第三层的遍历,求区间[j-1,min(w,j-1+s)]的最大值,可以使用单调队列优化,降低时间复杂度。

//单调队列优化
#include <iostream>
#include <deque>
using namespace std;
long long n, w, s;
long long a[5505];
long long dp[5505][5505];

int main()
{
	cin >> n >> w >> s;
	for (long long i = 1; i <= n; i++)
		cin >> a[i];
	for (long long i = 0; i <= n; i++)
		for (long long j = 0; j <= w; j++)
			dp[i][j] = -1e18;
	dp[0][0] = 0;

	//dp[i][j]选第i个材料时,此时正好j个材料的最大耐久度
	for (long long i = 1; i <= n; i++)
	{
		deque<long long> q;  //单调队列,记录区间最大值  存储索引
		q.push_back(w);      //下面循环中w不会进队列,需提前进队列
		//[j-1,min(j-1+s,w)]
		//从右向左遍历,因为左端点固定,而右端点使用了min
		for (long long j = w; j >= 1; j--)
		{
			//[j-1,min(j-1+s,w)]
			while (!q.empty() && q.front() > min(w, s + j - 1))
				q.pop_front();
			while (!q.empty() && dp[i - 1][q.back()] < dp[i - 1][j-1])
				q.pop_back();

			q.push_back(j-1); //比较的是q.back()和j-1位置

			//统计结果
			dp[i][j] = a[i] * j + dp[i - 1][q.front()];
		}
	}

	long long ans = -1e18;
	for (int i = 0; i <= w; i++)
		ans = max(ans, dp[n][i]);
	cout << ans << endl;

	return 0;
}

例题 

题目链接:1438. 绝对差不超过限制的最长连续子数组 - 力扣(LeetCode) 

 使用两个单调队列来维护窗口的最大值和最小值,结合递增队列和递减队列。当最大值减最小值超出给定的limit时,窗口的左边界右移动,直到找到符合的位置,统计结果。

class Solution {
public:
    int longestSubarray(vector<int>& nums, int limit) {
        //单调队列,维护当前窗口的最大值和最小值
        deque<int> queMax,queMin;
        int n=nums.size();

        int left=0,right=0,ret=0;
        while(right<n)
        {
            //维护队列的单调性
            //递减
            while(!queMax.empty()&&queMax.back()<nums[right])
            queMax.pop_back();
            //递增
            while(!queMin.empty()&&queMin.back()>nums[right])
            queMin.pop_back();

            //入队列元素
            queMin.push_back(nums[right]);
            queMax.push_back(nums[right]);
            while(!queMin.empty()&&!queMax.empty()&&queMax.front()-queMin.front()>limit)
            {
                if(nums[left]==queMin.front())
                queMin.pop_front();
                if(nums[left]==queMax.front())
                queMax.pop_front();
                
                left++;
            }
            ret=max(ret,right-left+1);
            right++;
        }
        return ret;
    }
};

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

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

相关文章

Mysql-------事务

事务 一、事务 &#xff08;一&#xff09;什么是事务&#xff1a; MySQL数据库事务&#xff1a;&#xff08;database transaction&#xff09;: 事务是由一组SQL语句组成的逻辑处理单元&#xff0c;这些操作要么全做要么全不做&#xff0c;是一个不可分割的工作单位。 ※…

基于Martin的全国基础底图实现

概述 前面有文章基于Martin实现MapboxGL自定义底图分享了Martin的使用&#xff0c;本文使用网络收集的数据实现了全国基础数据的收集和基础底图。 实现后效果 实现 1. 数据准备 实例中包含如下数据&#xff1a; 边界线和九段线数据省边界面数据省会城市点数据市边界面数据…

网络安全:防范NetBIOS漏洞的攻击

稍微懂点电脑知识的朋友都知道&#xff0c;NetBIOS 是计算机局域网领域流行的一种传输方式&#xff0c;但你是否还知道&#xff0c;对于连接互联网的机器来讲&#xff0c;NetBIOS是一大隐患。 漏洞描述 NetBIOS(Network Basic Input Output System&#xff0c;网络基本输入输…

一周学会Flask3 Python Web开发-客户端状态信息Cookie以及加密

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili HTTP是无状态&#xff08;stateless)协议。也就是说&#xff0c;在一次请求响应结束后&#xff0c;服务器不会留下任何关于对…

机器学习面试八股文——决战金三银四

大家好&#xff0c;这里是好评笔记&#xff0c;公主 号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本笔记的任务是解读机器学习实践/面试过程中可能会用到的知识点&#xff0c;内容通俗易懂&#xff0c;入门、实习和校招轻松搞定。 公主号合集地址 点击进入优惠地…

【Akashic Records】THE EGG

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: Akashic Records 文章目录 &#x1f4af;观后感一、宇宙的孤寂与个人成长&#xff1a;二、选择与责任&#xff1a;三、灵性与世界的连接&#xff1a;四、选择如何改变命运&#xff1a;结语&#xff1a; &#x1f4af;…

从零开始用react + tailwindcs + express + mongodb实现一个聊天程序(一)

项目包含5个模块 1.首页 (聊天主页) 2.注册 3.登录 4.个人资料 5.设置主题 一、配置开发环境 建立项目文件夹 mkdir chat-project cd chat-project mkdir server && mkdir webcd server npm init cd web npm create vitelatest 创建前端项目时我们选择javascrip…

ant design 疑惑记录 Dropdown.Button

onMenuClick是点击展开的 子项的点击事件 Actions的点击事件是什么&#xff1f; 解答&#xff1a; 也是个按钮Button&#xff0c;也有自己的onClick事件 const onMenuClick (e) > {console.log(click, e); }; const otherClick (e) > {console.log(其他操作主按钮…

SAP on Microsoft Azure Architecture and Administration (Ravi Kashyap)

SAP on Microsoft Azure Architecture and Administration (Ravi Kashyap)

Missing required prop: “maxlength“

背景&#xff1a; 封装一个使用功能相同使用频率较高的input公共组件作为子组件&#xff0c;大多数长度要求为200&#xff0c;且实时显示统计子数&#xff0c;部分input有输入提示。 代码实现如下&#xff1a; <template><el-input v-model"inputValue" t…

在windows下安装windows+Ubuntu16.04双系统(下)

这篇文章的内容主要来源于这篇文章&#xff0c;为正式安装windowsUbuntu16.04双系统部分。在正式安装前&#xff0c;若还没有进行前期准备工作&#xff08;1.分区2.制作启动u盘&#xff09;&#xff0c;见《在windows下安装windowsUbuntu16.04双系统(上)》 二、正式安装Ubuntu …

数据库驱动免费下载(Oracle、Mysql、达梦、Postgresql)

数据库驱动找起来好麻烦&#xff0c;我整理到了一起&#xff0c;需要的朋友免费下载&#xff1a;驱动下载 目前收录了Oracle、Mysql、达梦、Postgresql的数据库驱动的多个版本&#xff0c;后续可能会分享更多。

业务流程相关的权威认证和培训有哪些

业务流程的认证和培训种类繁多&#xff0c;旨在帮助专业人士掌握业务流程管理 (BPM) 的知识和技能&#xff0c;从而提升个人职业发展和组织运营效率。下面分别介绍&#xff1a; 一、 业务流程认证和培训的种类 业务流程的认证和培训可以大致分为以下几类&#xff0c;涵盖了不…

vivado修改下载器下载速率

Error Launching Program X Error while launching program: fpga configuration failed. DONE PIN is not HIGH 原因是下载器速度太快了。先从任务管理器中关闭hw_server.exe试一下,要是不行就按下面三种方法解决。 第一种方法可以不用修改下载速度,直接先从vivado中将bit流…

巧妙实现右键菜单功能,提升用户操作体验

在动态交互式图库中&#xff0c;右键菜单是一项能够显著提升用户操作便捷性的功能。它的设计既要响应用户点击位置&#xff0c;又需确保菜单功能与数据操作紧密结合&#xff0c;比如删除图片操作。以下将通过一段实际代码实现&#xff0c;展示从思路到实现的详细过程。 实现右键…

CASS11快捷键设置

快捷键增加如下&#xff1a; tr----trim bo---(-boundary) ro---rotate ed----explode of---offset qs---qselect dp---ptype re---regen rec---rectang br---break dis---distuser do---draworder

HTTP 常见状态码技术解析(应用层)

引言 HTTP 状态码是服务器对客户端请求的标准化响应标识&#xff0c;属于应用层协议的核心机制。其采用三位数字编码&#xff0c;首位数字定义状态类别&#xff0c;后两位细化具体场景。 状态码不仅是服务端行为的声明&#xff0c;更是客户端处理响应的关键依据。本文将从协议规…

如何使用3D高斯分布进行环境建模

使用3D高斯分布来实现建模&#xff0c;主要是通过高斯分布的概率特性来描述空间中每个点的几何位置和不确定性。具体来说&#xff0c;3D高斯分布被用来表示点云数据中的每一个点或体素&#xff08;voxel&#xff09;的空间分布和不确定性&#xff0c;而不是单纯地存储每个点的坐…

兰州百合基因组(36.68 Gb)-文献精读113

The evolutionary tale of lilies: Giant genomes derived from transposon insertions and polyploidization 百合的进化故事&#xff1a;由转座子插入和多倍化导致的巨型基因组 百合&#xff08;Lilium spp.&#xff09;&#xff0c;被誉为“球根花卉之王”&#xff0c;因其…

如何做实用、好用的矿用电子围栏(续篇)

1 写在前面 去年写了一篇文章《如何做实用、好用的矿用电子围栏》&#xff0c;初步探讨了一下矿用UWB的电子围栏的实现方法&#xff0c;阅读量超出预期&#xff0c;说明电子围栏需求度比较高&#xff0c;企业对安全生产越来越重视。当时写这篇文章时&#xff0c;由于时间和理解…