刷题篇-栈

末鹿安然 提交于 2020-01-11 23:27:55

一、设计一个最小栈

题目要求:设计一个支持push,pop,top等操作并且可以在O(1)O(1)时间内检索出最小元素的堆栈。

  • push(x)–将元素x插入栈中
  • pop()–移除栈顶元素
  • top()–得到栈顶元素
  • getMin()–得到栈中最小元素

样例

MinStack minStack = new MinStack();
minStack.push(-1);
minStack.push(3);
minStack.push(-4);
minStack.getMin();   --> Returns -4.
minStack.pop();
minStack.top();      --> Returns 3.
minStack.getMin();   --> Returns -1.

来源:剑指offer
思路:主要是完成最后一个功能getMin()–得到栈中最小元素,其他三个功能正常栈中都有。可以用两个栈来完成,一个栈是正常的栈,另一个保存前i个数的最小值。例如原栈stack<int> s={-1, 3, -4},则辅助栈stack<int> t={-1, -1, -4}

  • 当插入xx时,s={-1, 3, -4, x}, t = {-1, -1, -4, min(x, -4)}
  • 当移除栈顶元素时将s,t栈顶元素出栈即可
  • 得到栈中最小元素时返回辅助栈的栈顶元素即可

程序(CPP)

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> s, t;
    MinStack() {
        
    }
    
    void push(int x) {
        s.push(x);
        if(!t.empty()) t.push(min(t.top(), x));
        else t.push(x);
    }
    
    void pop() {
        s.pop();
        t.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int getMin() {
        return t.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

二、编辑器

题目要求:你将要实现一个功能强大的整数序列编辑器。在开始时,序列是空的。编辑器共有五种指令,如下:

  • 1、“I x”,在光标处插入数值x。
  • 2、“D”,将光标前面的第一个元素删除,如果前面没有元素,则忽略此操作。
  • 3、“L”,将光标向左移动,跳过一个元素,如果左边没有元素,则忽略此操作。
  • 4、“R”,将光标向右移动,跳过一个元素,如果右边没有元素,则忽略次操作。
  • 5、“Q k”,假设此刻光标之前的序列为a1,a2,…,an,输出max1≤i≤kSi,其中Si=a1+a2+…+ai。

输入格式
第一行包含一个整数Q,表示指令的总数。
接下来Q行,每行一个指令,具体指令格式如题目描述。

输出格式
每一个“Q k”指令,输出一个整数作为结果,每个结果占一行。

数据范围
1Q1061≤Q≤10^6
x103,|x|≤10^3,
1kn1≤k≤n

输入样例

8
I 2
I -1
I 1
Q 3
L
D
R
Q 2

输出样例

2
3

思路:用两个栈来维护:左边的栈sl和右边的栈sr

  • 当插入一个元素的时候相当于左边的栈sl插入一个元素,同时需要更新前缀和数组s及前缀和数组的前i个数的最小值数组f
  • 当删除一个元素时相当于删除左边的栈sl的栈顶元素
  • 当向左移动时相当于左边的栈sl的栈顶元素出栈,并将该元素放入右边的栈sr入栈
  • 当向右移动时时同理
  • 查询时输出前缀和数组的前i个数的最小值数组f对应位置的值即可

程序(CPP)

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include <limits.h>
using namespace std;

const int maxn = 1e6+10;
int sl[maxn], sr[maxn], tl, tr;  // 左右两个栈及其栈顶位置
int s[maxn], f[maxn]; // 前缀和数组和保留前i个数的最小值
void push_l(int x)
{
    sl[++tl] = x;
    s[tl] = s[tl-1]+x;
    f[tl] = max(f[tl-1], s[tl]);
}

int main()
{
    int Q; cin >> Q;
    f[0] = INT_MIN;
    while(Q--)
    {
        char t; cin >> t;
        int x;
        if(t=='I')
        {
            cin >> x;
            push_l(x);
        }
        else if(t=='D')
        {
            if(tl>0) tl--;
        }
        else if(t=='L')
        {
            if(tl>0) sr[++tr] = sl[tl--];
        }
        else if(t=='R')
        {
            if(tr>0) push_l(sr[tr--]);
        }
        else if(t=='Q')
        {
            cin >> x;
            cout << f[x] << endl;
        }
    }
    return 0;
}

三、火车进栈

题目描述
\quad这里有n列火车将要进站再出站,但是,每列火车只有1节,那就是车头。
\quad这n列火车按1到n的顺序从东方左转进站,这个车站是南北方向的,它虽然无限长,只可惜是一个死胡同,而且站台只有一条股道,火车只能倒着从西方出去,而且每列火车必须进站,先进后出。
\quad也就是说这个火车站其实就相当于一个栈,每次可以让右侧头火车进栈,或者让栈顶火车出站。

输入格式
输入一个整数n,代表火车数量。

输出格式
按照《字典序》输出前20种答案,每行一种,不要空格。

数据范围
1n201≤n≤20

输入样例

3

输出样例

123
132
213
231
321

思路1
\quad因为只需要枚举20种情况,因此可以枚举出前n个数的排列组合再依次判断每种组合是否是合法的出栈顺序。判断一种出栈顺序是否合法算法如下:

  • 同时使用一个队列q和一个堆栈s来解决该问题,其中,q存储待判断的出栈序列,而s用于模拟序列中每个元素的入栈和出栈过程。
  • 这里按照1-n的顺序将每个元素压入栈s:
  • 每次压入一个元素,检查栈顶元素与队列头元素是否相同,若相同,s与q同时执行pop操作。
  • 若最终栈s为空,说明q里存放的序列是合理的出栈顺序,否则就是不合理的出栈序列。

程序(CPP)

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;

int n;
vector<int> a;
// 判断出栈顺序是否合法
bool check()
{
    stack<int> s;
    queue<int> q;
    for(int i = 0; i < n; i++) q.push(a[i]);
    for(int i = 1; i <= n; i++)
    {
        s.push(i);
        while(!s.empty() && s.top()==q.front()) s.pop(), q.pop();
    }
    if(s.size()==0) return true;
    else return false;
}
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
        a.push_back(i);
    int cnt = 0;
    do
    {
        if(check())
        {
            for(int i = 0; i < n; i++) cout << a[i];
            cout << endl;
            cnt++;
            if(cnt==20) break;
        }
    }while(next_permutation(a.begin(), a.end()));
    return 0;
}

四、火车进出栈问题

题目描述
一列火车n节车厢,依次编号为1,2,3,…,n。
每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种。

输入格式
输入一个整数n,代表火车的车厢数。

输出格式
输出一个整数s表示n节车厢出栈的可能排列方式数量。

数据范围
1n600001≤n≤60000

输入样例

3

输出样例

5

思路:推导数学公式,是Catalan数,需要用到高精度。Catn=C2nnn+1Cat_n=\frac{C_{2n}^n}{n+1}
程序(CPP)

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

void multi(vector<long long> &a, int b)
{
    long long t = 0;
    for(int i = 0; i < a.size(); i++)
    {
        a[i] = a[i]*b+t;
        t = a[i] / 1000000000;
        a[i] %= 1000000000;
    }
    while(t)
    {
        a.push_back(t%1000000000);
        t /= 1000000000;
    }
}
void di(vector<long long> &a, int b)
{
    long long t = 0;
    for(int i = a.size()-1; i >= 0; i--)
    {
        a[i] += t*1000000000;
        t = a[i]%b;
        a[i] /= b;
    }
    while(a.size()>1 && a.back()==0) a.pop_back();
}
int main()
{
    int n; cin >> n;
    vector<long long> res;
    res.push_back(1);
    for(int i = 2*n, j=1; j<=n; i--, j++)
    {
        multi(res, i);
        di(res, j);
    }
    di(res, n+1);
    printf("%lld", res.back());
    for(int i = res.size()-2; i >= 0; i--)
    {
        printf("%09lld", res[i]);
    }
    return 0;
}

五、直方图中最大的矩形

题目描述
\quad直方图是由在公共基线处对齐的一系列矩形组成的多边形。矩形具有相等的宽度,但可以具有不同的高度。
\quad例如,图例左侧显示了由高度为2,1,4,5,1,3,3的矩形组成的直方图,矩形的宽度都为1。
在这里插入图片描述
\quad通常,直方图用于表示离散分布,例如,文本中字符的频率。现在,请你计算在公共基线处对齐的直方图中最大矩形的面积。图例右图显示了所描绘直方图的最大对齐矩形。

输入格式
\quad输入包含几个测试用例。每个测试用例占据一行,用以描述一个直方图,并以整数n开始,表示组成直方图的矩形数目。然后跟随n个整数h1hnh_1,…,h_n。这些数字以从左到右的顺序表示直方图的各个矩形的高度。每个矩形的宽度为1。同行数字用空格隔开。当输入用例为n=0时,结束输入,且该用例不用考虑。

输出格式
\quad对于每一个测试用例,输出一个整数,代表指定直方图中最大矩形的区域面积。
每个数据占一行。请注意,此矩形必须在公共基线处对齐。

数据范围
1n1000001≤n≤100000,
0hi10000000000≤h_i≤1000000000

输入样例

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

输出样例

8
4000

思路1:暴力枚举,枚举n2n^2个子矩形,时间复杂度为O(n2)O(n^2)

#include <iostream>
using namespace std;

const int maxn = 1e5+10;
long long a[maxn];

int main()
{
    int n; 
    while(cin >> n && n!=0)
    {
        for(int i = 1; i <= n; i++) cin >> a[i];
        long long res = 0;
        for(int i = 1; i <= n; i++)
        {
            long long h = a[i];
            for(int j = i; j <= n; j++)
            {
                h = min(h, a[j]);
                res = max(res, (j-i+1)*h);
            }
        }
        cout << res << endl;
    }
    return 0;
}

思路2:单调栈

#include <iostream>
using namespace std;

const int maxn = 1e5+10;
long long a[maxn], s[maxn], w[maxn];

int main()
{
    int n; 
    while(cin >> n && n!=0)
    {
        for(int i = 1; i <= n; i++) cin >> a[i];
        long long res = 0;
        int p = 0;
        a[n+1] = 0;
        for(int i = 1; i <= n+1; i++)
        {
            if(a[i]>s[p]) s[++p]=a[i], w[p]=1;
            else
            {
                int width = 0;
                while(s[p]>a[i])
                {
                    width += w[p];
                    res = max(res, width*s[p]);
                    p--;
                }
                s[++p] = a[i], w[p] = width+1;
            }
        }
        cout << res << endl;
    }
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!