菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
276
0

栈与队列

原创
05/13 14:22
阅读数 48028

2、栈与队列

2.1 栈

先进后出

用tt表示栈顶指针,tt--弹出,tt > 0说明非空

stk[++tt]插入,stk[tt]就是栈顶元素

2.1.1 练手题目

2.1.2 练手答案
#include <iostream>

using namespace std;

const int N = 100010;

int m;
// tt表示栈顶指针,tt--弹出,tt > 0说明非空 
int stk[N], tt; 

int main()
{
    cin >> m;
    while (m -- )
    {
        string op;
        int x;

        cin >> op;
        if (op == "push")
        {
            cin >> x;
            stk[ ++ tt] = x;
        }
        else if (op == "pop") tt -- ;
        else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;
        else cout << stk[tt] << endl;
    }

    return 0;
}

2.2 队列

先进先出

hh 表示队头,tt表示队尾,,在队尾插入元素,队头弹出元素

hh = 0, tt = -1;

// 向队尾插入一个数
q[ ++ tt] = x;

// 从队头弹出一个数
hh ++ ;

// 队头的值
q[hh];

// 队尾

q[tt];

// 判断队列是否为空
if (hh <= tt)
{

​ return "not empty";

}

2.2.1 练手题目
2.2.2 练手答案
#include <iostream>

using namespace std;

const int N = 100010;

int m;
// 在队尾插入,队头弹出 
// hh队头,tt队尾 
int q[N], hh, tt = -1;

int main()
{
    cin >> m;

    while (m -- )
    {
        string op;
        int x;

        cin >> op;
        if (op == "push")
        {
            cin >> x;
            q[ ++ tt] = x;
        }
        else if (op == "pop") hh ++ ;
        else if (op == "empty") cout << (hh <= tt ? "NO" : "YES") << endl;
        else cout << q[hh] << endl;
    }

    return 0;
}

2.3 单调栈

一般只有这些问题:

给定一个序列,求序列中每一个数左/右边离他最近且比他小/大的数在什么位置

2.3.1 练手题目

暴力做法:

这种暴力做法用栈的思想就是:

把i前面的所有元素\(a_1,a_2,...a_{i-1}\)都放到了stack里,不断遍历

考虑单调栈优化:

暴力做法的栈里面有些元素永远都不会输出出来,我们也去遍历了

如在栈里\(a_3 \geq a_5\),那么\(a_3\)永远都不会输出出来,那么所有满足这种关系的序列都可以删除掉。

最后获得的一定是一个严格单调的序列

在这个情况下,当到i这个位置时,如果stk[tt]\(\geq a_i\),那么stk[tt]就可以删除了,因为i永远都是比stk[tt]更优的解。如果stk[tt]\(< a_i\),那么stk[tt]就是i的最优解。

2.3.2 练手答案
#include<iostream>

using namespace std;

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

int main()
{
    cin >> n;

    for(int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        while(tt && stk[tt] >= x) tt--;
        if(tt) cout << stk[tt] << ' ';
        else cout << -1 << ' ';

        stk[++tt] = x;
    }
    return 0;
}

2.4 单调队列

经典应用:求滑动窗口的最大值和最小值

如下图的框框中的最大值/最小值

能抽象出这个模型就可以使用单调队列优化

使用队列来做:

首先有一个空队列,先进来1,之后进来3,之后进来-1.

在窗口满了之后,弹出1,将下一个-3放进来.

实际就是将新元素插到队尾,同时弹出队头元素。

暴力做法:每次都遍历一次队列里的元素。O(nk),k为窗口内元素数量,n为步数,约等于O(\(n^2\)).

单调队列优化:求最小值时,如上图的窗口内,当-3进来时,第一个3一定没有用,-3在一天,这个3就永远没有用武之地这个-3还比3活得久,这个3就可以X掉,-1也同理。

总结规律:只要我前面的点比我后面的点大,那么这个前面的点就没有用

最后就可以得到一个严格单调上升的队列。

队列里存储的是下标。通过q[hh]判断是否超出了范围。

2.4.1 练手题目

https://www.acwing.com/problem/content/156/

2.4.2 练手答案
// 单调队列
// 凡是可以抽象出滑动窗口的,都可以用单调队列优化
// 这个窗口可以用队列维护,满3个就既有插入又有弹出,保证每次都是当前窗口
// 都遍历就是O(k),但我们需要的是最小的
// 只要前面有一个点比后面大,前面这个点一定就没用,把大的删掉


#include<iostream>

using namespace std;

const int N = 1000010;

int n,k;
int a[N]; // 存储数值
int q[N]; // 是队列,存储下标

int main()
{
    scanf("%d%d",&n,&k);
    for(int i = 0; i < n; i++) scanf("%d",&a[i]);

    int hh = 0,tt=-1; // 队头和队尾
    for(int i = 0; i < n; i++)
    {

        // 队列中存的是下标而不是值,判断队头下标是否超出i-k+1~i的范围
        // 超出就把队头删掉

        // 判断队头是否划出窗口,用下标判断
        // 终点是i,起点应该是i-k+1, > q[hh]说明已经出了这个口了
        if(hh <= tt && i-k+1 > q[hh]) hh++;
        while(hh <= tt && a[q[tt]] >= a[i]) tt--;

        q[++tt] = i;
        // 不足k个数就不输出了
        if(i >= k-1) printf("%d ",a[q[hh]]) ;
        
    }
    puts("");

    hh = 0,tt=-1; // 队头和队尾
    for(int i = 0; i < n; i++)
    {
        // 判断对头是否划出窗口
        if(hh <= tt && i-k+1 > q[hh]) hh++;
        while(hh <= tt && a[q[tt]] <= a[i]) tt--;

        q[++tt] = i;
        if(i >= k-1) printf("%d ",a[q[hh]]) ;
        
    }
    puts("");
    return 0;
}

发表评论

0/200
276 点赞
0 评论
收藏