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

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(使用前将#替换为@)

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

  • 如何在MVVM中管理多个窗口

    我知道有几个与此类似的问题 但我还没有找到明确的答案 我正在尝试深入研究 MVVM 并尽可能保持纯粹 但不确定如何在坚持模式的同时启动 关闭窗口 我最初的想法是向 ViewModel 发送数据绑定命令 触发代码来启动一个新视图 然后通过 X
  • 如何检查图像对象与资源中的图像对象是否相同?

    所以我试图创建一个简单的程序 只需在单击图片框中更改图片即可 我目前只使用两张图片 所以我的图片框单击事件函数的代码 看起来像这样 private void pictureBox1 Click object sender EventArgs
  • 如何使 Windows 窗体的关闭按钮不关闭窗体但使其不可见?

    该表单有一个 NotifyIcon 对象 当用户单击 关闭 按钮时 我希望表单不关闭而是变得不可见 然后 如果用户想再次查看该表单 可以双击系统托盘中的图标 如果用户想关闭表单 可以右键单击该图标并选择 关闭 有人可以告诉我如何使关闭按钮不
  • 如何验证文件名称在 Windows 中是否有效?

    是否有一个 Windows API 函数可以将字符串值传递给该函数 该函数将返回一个指示文件名是否有效的值 我需要验证文件名是否有效 并且我正在寻找一种简单的方法来完成此操作 而无需重新发明轮子 我正在直接使用 C 但针对的是 Win32
  • Qt-Qlist 检查包含自定义类

    有没有办法覆盖加载自定义类的 Qt QList 的比较机制 即在 java 中你只需要重写一个比较方法 我有一个带有我的自定义类模型的 QList QList
  • 从父类调用子类方法

    a doStuff 方法是否可以在不编辑 A 类的情况下打印 B did stuff 如果是这样 我该怎么做 class Program static void Main string args A a new A B b new B a
  • 如何避免情绪低落?

    我有一个实现状态模式每个状态处理从事件队列获取的事件 根据State因此类有一个纯虚方法void handleEvent const Event 事件继承基础Event类 但每个事件都包含其可以是不同类型的数据 例如 int string
  • 使闭包捕获的变量变得易失性

    闭包捕获的变量如何与不同线程交互 在下面的示例代码中 我想将totalEvents 声明为易失性的 但C 不允许这样做 是的 我知道这是错误的代码 这只是一个例子 private void WaitFor10Events volatile
  • 在 Visual Studio 2008 上设置预调试事件

    我想在 Visual Studio 中开始调试程序之前运行一个任务 我每次调试程序时都需要运行此任务 因此构建后事件还不够好 我查看了设置的 调试 选项卡 但没有这样的选项 有什么办法可以做到这一点吗 你唯一可以尝试的 IMO 就是尝试Co
  • WPF TabControl,用C#代码更改TabItem的背景颜色

    嗨 我认为这是一个初学者的问题 我搜索了所有相关问题 但所有这些都由 xaml 回答 但是 我需要的是后台代码 我有一个 TabControl 我需要设置其项目的背景颜色 我需要在选择 取消选择和悬停时为项目设置不同的颜色 非常感谢你的帮助
  • 指针减法混乱

    当我们从另一个指针中减去一个指针时 差值不等于它们相距多少字节 而是等于它们相距多少个整数 如果指向整数 为什么这样 这个想法是你指向内存块 06 07 08 09 10 11 mem 18 24 17 53 7 14 data 如果你有i
  • 如何将图像路径保存到Live Tile的WP8本地文件夹

    我正在更新我的 Windows Phone 应用程序以使用新的 WP8 文件存储 API 本地文件夹 而不是 WP7 API 隔离存储文件 旧的工作方法 这是我如何成功地将图像保存到 共享 ShellContent文件夹使用隔离存储文件方法
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • Github Action 在运行可执行文件时卡住

    我正在尝试设置运行google tests on a C repository using Github Actions正在运行的Windows Latest 构建过程完成 但是当运行测试时 它被卡住并且不执行从生成的可执行文件Visual
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • 如何在 VBA 中声明接受 XlfOper (LPXLOPER) 类型参数的函数?

    我在之前的回答里发现了问题 https stackoverflow com q 19325258 159684一种无需注册即可调用 C xll 中定义的函数的方法 我之前使用 XLW 提供的注册基础结构 并且使用 XlfOper 类型在 V
  • C++ 复制初始化和直接初始化,奇怪的情况

    在继续阅读本文之前 请阅读在 C 中 复制初始化和直接初始化之间有区别吗 https stackoverflow com questions 1051379 is there a difference in c between copy i
  • 将文本叠加在图像背景上并转换为 PDF

    使用 NET 我想以编程方式创建一个 PDF 它仅包含一个背景图像 其上有两个具有不同字体和位置的标签 我已阅读过有关现有 PDF 库的信息 但不知道 如果适用 哪一个对于如此简单的任务来说最简单 有人愿意指导我吗 P D 我不想使用生成的
  • Process.Start 阻塞

    我正在调用 Process Start 但它会阻止当前线程 pInfo new ProcessStartInfo C Windows notepad exe Start process mProcess new Process mProce
  • C 中的异或运算符

    在进行按位操作时 我在确定何时使用 XOR 运算符时遇到一些困难 按位与和或非常简单 当您想要屏蔽位时 请使用按位 AND 常见用例是 IP 寻址和子网掩码 当您想要打开位时 请使用包含或 然而 XOR 总是让我明白 我觉得如果在面试中被问

随机推荐