单调栈模拟队列的原理与实践

116 阅读4分钟

模拟队列

什么是队列?

  • 就是一个特殊的数组。这个数组,最前面叫队头,最后面叫队尾。只允许在最后面添加元素,只允许在最前面删除元素。

题目

实现一个队列,队列初始为空,支持四种操作:

  1. push x – 向队尾插入一个数 x ;
  2. pop – 从队头弹出一个数;
  3. empty – 判断队列是否为空;
  4. query –查询队头元素。

现在要对队列进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。

输入格式 第一行包含整数 M ,表示操作次数。

接下来 M 行,每行包含一个操作命令,操作命令为 push x,pop,empty,query 中的一种。

输出格式 对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。

其中,empty 操作的查询结果为 YES 或 NO,query 操作的查询结果为一个整数,表示队头元素的值。

数据范围 、 1≤M≤100000 , 1≤x≤109 , 所有操作保证合法。

输入样例: 10 push 6 empty query pop empty push 3 push 4 pop query push 6 输出样例: NO 6 YES 4

解题思路:

  1. 用一个数组 q 保存数据。

  2. hh 代表队头,q[hh] 就是队头元素, q[hh + 1] 就是第二个元素。

  3. tt 代表队尾, q[tt] 就是队尾元素, q[tt + 1] 就是下一次入队,元素应该放的位置。

  4. [hh, tt] 左闭右闭,代表队列中元素所在的区间。

  5. 出队pop:因为 hh 代表队头,[hh, tt] 代表元素所在区间。所以出队可以用 hh++实现,hh++后,区间变为[hh + 1, tt]

  6. 入队push:因为 tt 代表队尾,[hh, tt] 代表元素所在区间。所以入出队可以用 tt++实现,tt++后,区间变为[hh, tt + 1], 然后在q[tt+1]位置放入入队元素。

  7. 是否为空empty[hh, tt] 代表元素所在区间,当区间非空的时候,对列非空。也就是tt >= hh的时候,对列非空。

  8. 询问队头query:用 hh 代表队头,q[hh] 就是队头元素,返回 q[hh] 即可。

代码

#include <iostream>
using namespace std;
const int N = 100010;
int q[N];

//[hh, tt] 之间为队列(左闭右闭)
int hh = 0;//队头位置
int tt = -1;//队尾位置
//操作次数
int m;
//操作方式
string s;

//入队:队尾先往后移动一格,再放入要插入的数据
void push(int x){
    q[++tt] = x;
}
//出队:队头往后移动一格
void pop(){
    hh++;
}
//[hh, tt]表示队列区间,当tt >= hh时,区间不为空
void empty(){
    if(tt >= hh) cout << "NO" << endl;
    else cout << "YES" << endl;
} 
//hh指向队头,q[hh]代表队头元素
void query (){
    cout << q[hh] << endl;
}

int main(){
    cin >> m;
    while(m--){
        cin >> s;
        //入队
        if(s == "push"){
            int x;
            cin >> x;
            push(x);
        }
        //出队
        if(s == "pop"){
            pop();
        }
        //问空
        if(s == "empty"){
            empty();
        }
        //问队头
        if(s == "query"){
            query();
        }
    }
}

Java

import java.util.Scanner;
public class Main{
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int m = scan.nextInt();
        //队列是在tt队尾插入元素,队头hh弹出元素
        int[] dl = new int[100010];
        int hh = 0;
        int tt = -1;
        while(m -- > 0){
            String s = scan.next();
            if(s.equals("push")){
                int x = scan.nextInt();
                dl[++tt] =  x;
            }else if(s.equals("pop")){
                hh++;
            }else if(s.equals("empty")){
                if(hh <= tt) System.out.println("NO");
                else System.out.println("YES");
            }else {
                System.out.println(dl[hh]);
            }
        }
    }
}

单调栈

给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1 。

输入格式 第一行包含整数 N ,表示数列长度。

第二行包含 N 个整数,表示整数数列。

输出格式 共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1 。

数据范围 1≤N≤10^5^

1≤数列中元素≤10^9^ 输入样例: 5 3 4 2 7 5 输出样例: -1 3 -1 2 2

算法原理:

用单调递增栈,当该元素可以入栈的时候,栈顶元素就是它左侧第一个比它小的元素。 以:3 4 2 7 5 为例,过程如下:

代码:

//cpp
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        while (tt && stk[tt] >= x) tt -- ;//如果栈顶元素大于当前待入栈元素,则出栈
        if (!tt) printf("-1 ");//如果栈空,则没有比该元素小的值。
        else printf("%d ", stk[tt]);//栈顶元素就是左侧第一个比它小的元素。
        stk[ ++ tt] = x;
    }
    return 0;
}

Java

import java.util.*;
public class Main{
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();

        int[] stk  = new int[100010];
        int tt = 0;

        for(int i = 0 ; i < n ;  i++ ){
            int x = scan.nextInt();
            //如果栈是空的,栈顶元素大于等于x,那么就说明栈顶这个数明显没有x好,
            //因为比较的是左边第一个最近最小的数,所以就把stk[tt]弹出了;
            while(tt!=0 && stk[tt] >= x){
                tt--;
            }
            //如果弹出操作完了之后,栈不是空的,就输出栈顶元素,
            if(tt != 0) System.out.print(stk[tt]+" ");
            //否则就是栈是空的,输出-1
            else System.out.print("-1" + " ");
            //最后将x插入栈顶;
            stk[++tt] = x;
        }
    }
}