单调栈和滑动窗口【题解】

2023-11-11

单调栈

单调栈,顾名思义就是具有单调性的栈。通常是用来求数组中的左边第一个比自身小或者大的数,使用场景还是比较有限的。但是对于解题还是有着很大的帮助的。


我们还是通过题目来进行讲解。
在这里插入图片描述

对于这种题目,首先想到的都是暴力求解

枚举每一个位置,然后从这个位置的前一个位置元素遍历,找到第一个小于它的元素的位置就是我们所求的。

那么这时的时间复杂度就会是 O(n^2) 效率很低。


思路优化:

我们可以发现这不就是我们从前往后枚举,然后从后往前遍历么 —— 这就符合了栈的先进后出。那么就可以在从后往前遍历的时候构建一个单调栈,将大于它的数都出栈,那么这个栈就是一个单调递增的单调栈。

如果当前位置有解,这么这个解一定是栈顶的元素 —— 因为大于它的我们都弹栈了, 然后当前元素又入栈,我们发现,栈里面栈顶元素的解一定是栈顶元素下面的那个元素。

利用这个性质,我们比较时就可以跳过许多不需要比较的位置了。

比如我们现在要入栈的是 8, 然后栈顶元素是 7, 那么答案一定是 7, 因为 7 一定是最后入栈的。如果栈顶元素是 9,那么 9 弹出,此时栈顶是 6, 那么答案就是 6, 因为 6 一定是距离 9 最近的并且小于它的元素。其他位置的元素要么大于 9, 要么距离远,都不符合条件。


代码

#include <iostream>

using namespace std;

const int N = 100010;
int a[N], n, tt;


int main()
{
    tt = -1;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        
        while (tt > -1 && a[tt] >= x) tt--;	// 将栈顶大于a[i]的数出栈,保持单调性
        if (tt == -1) cout << -1 << ' ';
        else cout << a[tt] << ' ';
        a[++tt] = x;	
    } 
    return 0;
}

滑动窗口(单调队列)

在这里插入图片描述

暴力求解:

我们还是先来看一下使用暴力求解的做法:

枚举每个区间,然后在区间中寻找最大值和最小值。

这时的时间复杂度就是 O(n * k) 效率还是比较低的。


思路优化(最小值为例):

我们可以根据暴力求解的规律发现,每个元素都是先进先出的 —— 满足队列的性质,那么我们就可以通过队列来维护每一个区间。

如果队列中存在两个元素,满足 a[i] >= a[j]i < j,那么无论在什么时候我们都不会取a[i]作为最小值了(因为 ji 活得久),所以可以直接将 a[i] 删掉。

此时队列中剩下的元素严格单调递增,所以队头就是整个队列中的最小值,可以用 O(1) 的时间找到。

为了维护队列的这个性质,我们在往队尾插入元素之前,先将队尾大于等于当前数的元素全部弹出即可。

这样所有数均只进队一次,出队一次,所以时间复杂度是 O(n) 的。


代码

#include <iostream>

using namespace std;

const int N = 1e6 + 10;
int a[N], q[N];     // q只是用来维护a的队列,存放的是下标
int head, tail = -1;
int n, k;

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i++)
        cin >> a[i];
        
    // 寻找最小值
    // 先移动左端点,后移动右端点
    for (int i = 0; i < n; i++)
    {
        // 因为确定了每次只往后移动一位,所以用if
        // 当队列中有元素,并且队头的元素小于区间的左端点的时候右移
        if (tail >= head && i - k + 1 > q[head]) head++;
        
        // 找最小值,需要使用递增队列
        // 通过观察到的规律进行单调性的维护
        while (tail >= head && a[q[tail]] >= a[i]) tail--;
        q[++tail] = i;
        
        if (i >= k - 1) cout << a[q[head]] << ' ';  // 控制区间输出
    }
    puts("");
    
    head = 0, tail = -1;
    for (int i = 0; i < n; i++)
    {
        if (tail >= head && i - k + 1 > q[head]) head ++;
        while (tail >= head && a[q[tail]] <= a[i]) tail --; // 只需要改变队列的递增性质
        q[++tail] = i;
        
        if (i >= k - 1) cout << a[q[head]] << ' ';
    }
    
    
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单调栈和滑动窗口【题解】 的相关文章

  • 以相反的顺序迭代可变参数模板参数

    如果我手动反转传递给它的模板参数的顺序 以下代码将起作用 template
  • 扫描文本文件时如何跳过行?

    我想扫描一个文件并在阅读之前跳过一行文本 我试过 fscanf pointer n struct test i j 但这个语法只是从第一行开始 我可以使用 scanf 使用以下指令跳过行 fscanf config file n n 格式字
  • C修改printf()输出到文件

    有没有办法修改printf为了将字符串输出到文件而不是控制台 我尝试在互联网上查找一些内容 发现了类似的电话dup dup2 and fflush这可能与此有关 EDIT 也许我不清楚 问题是这是C考试问题 问题如下 解释一个通常将字符串输
  • 如何将pdf页面设置设置为打印属性对话框?

    大家好 我想知道如何设置 pdf 页面设置到打印属性对话框 例如 如果我的 PDF 页面设置为横向 则布局会自动显示横向而不是纵向 如果我的 PDF 页面设置为纵向 则布局会自动显示纵向 我在这个主题上做了很多研发 但没有找到任何满意的链接
  • 返回 int& 的函数[重复]

    这个问题在这里已经有答案了 我在网上查了一下发现一篇试图解释的文章std move和右值 http thbecker net articles rvalue references section 01 html并发现了一些我实在无法掌握的东
  • 关闭 XDOCUMENT 的实例

    我收到这个错误 该进程无法访问文件 C test Person xml 因为它是 被另一个进程使用 IOException 未处理 保存文件内容后如何关闭 xml 文件的实例 using System using System Collec
  • F10键没被抓住

    I have a Windows Form and there overriden ProcessCmdKey However this works with all of the F Keys except for F10 I am tr
  • 使用反射获取基类的受保护属性值

    I would like to know if it is possible to access the value of the ConfigurationId property which is located in the base
  • 特定设备的不同字体大小

    我目前正在开发通用应用程序 我需要分别处理移动设备和桌面的文本框字体大小 我找到了一些方法 但都不能解决问题 使用 VisualStateManager 和 StateTrigger 为例
  • 使用 openssl 检查服务器安全协议

    我有一个框架应用程序 它根据使用方式连接到不同的服务器 对于 https 连接 使用 openssl 我的问题是 我需要知道我连接的服务器是否使用 SSL 还是 TLS 以便我可以创建正确的 SSL 上下文 目前 如果我使用错误的上下文尝试
  • 使用scanf()时如何区分整数和字符

    我只是使用该功能scanf 代码如下 scanf d a printf d a 当我输入1时 它会像我想要的那样打印1 但即使我输入 1a 它也会像以前一样打印 1 当用户输入非整数时 例如 2 3 12ab 1 a 我想向用户显示 输入整
  • c# 如何生成锦标赛括号 HTML 表

    所以我已经被这个问题困扰了三个星期 但我一生都无法弄清楚 我想做的是使用表格获得这种输出 演示 http www esl world net masters season6 hanover sc2 playoffs rankings htt
  • 数据损坏 C++ 和 Python 之间的管道

    我正在编写一些代码 从 Python 获取二进制数据 将其通过管道传输到 C 对数据进行一些处理 在本例中计算互信息度量 然后将结果通过管道传输回 Python 在测试时 我发现如果我发送的数据是一组尺寸小于 1500 X 1500 的 2
  • 如何在 C# 中使用 XmlDsigC14NTransform 类

    我正在尝试使用规范化 xml 节点System Security Cryptography Xml XMLDsigC14nTransformC net Framework 2 0 的类 该实例需要三种不同的输入类型 NodeList Str
  • 为什么WCF中不允许方法重载?

    假设这是一个ServiceContract ServiceContract public interface MyService OperationContract int Sum int x int y OperationContract
  • 你能解释一下这个C++删除问题吗?

    我有以下代码 std string F WideString ws GetMyWideString std string ret StringUtils ConvertWideStringToUTF8 ws ret return ret W
  • 如何将 CSV 文件读入 .NET 数据表

    如何将 CSV 文件加载到System Data DataTable 根据CSV文件创建数据表 常规 ADO net 功能是否允许这样做 我一直在使用OleDb提供者 但是 如果您正在读取具有数值的行 但希望将它们视为文本 则会出现问题 但
  • 将一个 long 转换为两个 int 以进行重构

    我需要将一个参数作为两个 int 参数传递给 Telerik Report 因为它不能接受长参数 将 long 拆分为两个 int 并在不丢失数据的情况下重建它的最简单方法是什么 使用掩蔽和移位是最好的选择 根据文档 long 保证为 64
  • 程序退出后,TcpListener Socket 仍处于活动状态

    当我的程序退出时 我试图停止 TCP 侦听器 我不关心套接字或任何活动客户端套接字上当前活动的任何数据 套接字清理代码本质上是 try myServer Server Shutdown SocketShutdown Both catch E
  • 如何从函数返回矩阵(二维数组)? (C)

    我创建了一个生成宾果板的函数 我想返回宾果板 正如我没想到的那样 它不起作用 这是函数 int generateBoard int board N M i j fillNum Boolean exists True initilize se

随机推荐