AcWing 830. 单调栈

该博客介绍了如何使用单调栈优化算法解决寻找数组中每个元素左边或右边第一个小于它的数的问题。通过维护栈内元素单调性,避免暴力双层循环的高时间复杂度。文章详细分析了单调栈的工作原理,并提供了C++代码实现,阐述了在输入元素时如何调整栈的状态以达到优化效果。
摘要由CSDN通过智能技术生成

题目描述


分析:

本题是单调栈经典且少数的应用。维护栈内元素的单调性是有助于求解左边/右边第一个比它小/大的数这样的问题,与暴力双层 for 循环相比能够大量减少比较次数。我们先看一下暴力解的方法:

for (int i = 0; i < n; i ++)
	for (int j = i - 1; j >= 0; j --)
		// compare and select

很明显这是一个 O ( n 2 ) O(n^2) O(n2) 的复杂度。在内层的 for 循环中,有时会浪费大量时间。比如看下面的分析:

假设输入为 S k S_{k} Sk,对其分析有两种情况:

  1. S k > S k − 1 S_{k} > S_{k-1} SkSk1,输出 S k − 1 S_{k-1} Sk1 即可。
  2. S k ≤ S k − 1 S_k ≤ S_{k-1} SkSk1,要考虑的就多了!
    ① 当 S k ≤ S k − 1 S_k ≤ S_{k-1} SkSk1 S k ≤ [ S k − 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S 1 ] S_k ≤ [ S_{k-1}··· ···S_{1}] Sk[Sk1⋅⋅⋅⋅⋅⋅S1] 时( S k − 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S 1 S_{k-1}··· ···S_{1} Sk1⋅⋅⋅⋅⋅⋅S1 大小关系并非具有单调性,可以对其任意一个元素都满足这种小于关系)。对于输入的 S k S_{k} Sk,要输出 − 1 -1 1
    ② 当 S k ≤ S k − 1 S_k ≤ S_{k-1} SkSk1 S k S_{k} Sk 大于 [ S k − 2 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S 1 S_{k-2}··· ···S_{1} Sk2⋅⋅⋅⋅⋅⋅S1] 中的某一个元素时。记为 S i S_{i} Si,说明 S k ≤ [ S k − 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S i + 1 ] S_k ≤ [ S_{k-1}··· ···S_{i+1}] Sk[Sk1⋅⋅⋅⋅⋅⋅Si+1] 的全部元素,但大于 S i S_{i} Si,此时应输出 S i S_{i} Si

请务必搞明白输入 S k S_{k} Sk 时 2.>② 中的内容,这是理解下面内容的关键。

假设输入为 S k + 1 S_{k+1} Sk+1,对其分析有两种情况:

  1. S k + 1 > S k S_{k+1} >S_{k} Sk+1Sk,输出 S k S_{k} Sk 即可。
  2. S k + 1 ≤ S k S_{k+1} ≤ S_{k} Sk+1Sk,要考虑的就更多了!
    ① 当 S k + 1 ≤ S k S_{k+1} ≤ S_{k} Sk+1Sk S k + 1 ≤ [ S k ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S 1 ] S_{k+1} ≤ [ S_{k}··· ···S_{1}] Sk+1[Sk⋅⋅⋅⋅⋅⋅S1],对于输入的 S k + 1 S_{k+1} Sk+1,要输出 − 1 -1 1
    ② 当 S k + 1 ≤ S k S_{k+1} ≤ S_{k} Sk+1Sk S k + 1 S_{k+1} Sk+1 大于 [ S k − 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S 1 S_{k-1}··· ···S_{1} Sk1⋅⋅⋅⋅⋅⋅S1] 中的某一个元素时。记为 S j S_{j} Sj,说明 S k ≤ [ S k ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S j + 1 ] S_k ≤ [ S_{k}··· ···S_{j+1}] Sk[Sk⋅⋅⋅⋅⋅⋅Sj+1] 的全部元素,但大于 S j S_{j} Sj,此时应输出 S j S_{j} Sj

我们来仔细分析一下输入 S k + 1 S_{k+1} Sk+1 时 2.>② 中的内容:

在这里插入图片描述
在这里插入图片描述

那么怎么对其优化呢?由上图我们可以得知 S k + 1 ≤ [ S k ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S i + 1 ] S_{k+1} ≤ [S_{k}··· ···S_{i+1}] Sk+1[Sk⋅⋅⋅⋅⋅⋅Si+1],即 [ S k ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ S i + 1 ] [S_{k}··· ···S_{i+1}] [Sk⋅⋅⋅⋅⋅⋅Si+1] 皆不能作为输入值为 S k + 1 S_{k+1} Sk+1 的输出,而在暴力做法中在这里会消耗大量的比较时间。这就是单调栈改进的地方,在不断出栈的过程中维护一个“合理”的区间。这要求在输入为 S k S_{k} Sk 时进行维护,而在输入为 S k + 1 S_{k+1} Sk+1 时享受这一成果。
具体步骤为:输入为 S k S_{k} Sk 时,若栈不为空,将栈中比 S k S_{k} Sk 大的元素全部出栈。这一定是有效的,因为:
S k + 1 S_{k+1} Sk+1 大于 S k S_{k} Sk,输出 S k S_{k} Sk
S k + 1 S_{k+1} Sk+1 小于等于 S k S_{k} Sk,那将更会小于出栈的元素,比了也没用!


代码(C++)

#include <iostream>

using namespace std;

const int N = 100010;
int stk[N], top; // top 为 0 时,栈为空

int main()
{
    int n;
    cin >> n;
    
    for (int i = 0; i < n; i ++)
    {
        int k;
        cin >> k;
        
        // 当栈非空且栈顶元素大于等于输入元素时,不断出栈,为下一个输入元素“谋福利”
        while (top && stk[top] >= k) top --;
        
        // 若栈非空,则栈顶元素为左边第一个小于输入 x 的元素
        if (top) cout << stk[top] << ' ';
        // 栈为空时没有目标值
        else cout << "-1 ";
        
        // 将当前值入栈作为后面输入的比较元素
        stk[++ top] = k;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值