包含min函数的栈

设计一个支持push,pop,top等操作并且可以在O(1)时间内检索出最小元素的堆栈。
push(x)–将元素x插入栈中
pop()–移除栈顶元素
top()–得到栈顶元素
getMin()–得到栈中最小元素

利用一个辅助栈维护最小元素

class MinStack {
public:

    stack<int> s, temp;
    int min_x;

    /** initialize your data structure here. */
    MinStack() {

    }
    
    void push(int x) {
        if(!s.size()) min_x = x;
        else min_x = temp.top();
        s.push(x);
        temp.push(min(x, min_x));
    }
    
    void pop() {
        s.pop();
        temp.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int getMin() {
        return temp.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,输出 1 ⩽ i ⩽ k m a x _{1\leqslant i\leqslant k}^{max} 1ikmaxSi,其中Si=a1+a2+…+ai。

将光标前后分成两部分,利用两个栈来维护,其中前面的栈栈顶元素是光标前面的元素,后面的栈栈顶元素是光标后面的元素。光标每次的移动就是两个栈分别的弹出和压入,删除就是前面栈的弹出,插入就是前面栈的弹出。而前缀和最大值只在光标右移和插入是更新前缀和,并于此前的最大值比较。

#include <iostream>
#include <limits.h>

using namespace std;

//front光标前的栈,back光标后的栈,sum前缀和
int front[1000010], back[1000010], sum[1000010];
//l,r分别指向栈顶元素
int l, r;

int main(){
	//n表示命令数,s表示当前前缀和总和
    sum[0] = INT_MIN;
    int n, num, s = 0;
    char c;
    cin >> n;
    while(n--){
        cin >> c;
        //插入操作,更新前缀和
        if(c == 'I'){
            cin >> num;
            front[l++] = num;
            s += num;
            sum[l] = max(sum[l-1], s);
        }
        //删除操作
        else if(c == 'D'){
            if(l != 0){
                s -= front[--l];
            }
        }
        //左移操作,前面栈的栈顶元素给到后面栈
        else if(c == 'L'){
            if(l != 0){
                back[r++] = front[--l];
                s -= front[l];
            }
        }
        //右移操作,右边栈的栈顶元素给到左边栈,同时更新前缀和
        else if(c == 'R'){
            if(r != 0){
                front[l++] = back[--r];
                s += front[l-1];
                sum[l] = max(sum[l-1], s);
            }
        }
        //取前缀和
        else if(c == 'Q'){
            cin >> num;
            cout << sum[num] << endl;
        }
    }
    
    return 0;
}
火车进站

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

       出站<——    <——进站
                |车|
                |站|
                |__|

现在请你按《字典序》输出前20种可能的出栈方案。

我们可以将火车进站分成两个过程,一个是入栈,一个是出栈,因此可以用递归遍历所有的可能,因为要按照字典序输出,所以深度优先搜索要优先递归出栈过程。

#include <iostream>
#include <stack>
#include <vector>

using namespace std;

// in表示站内的火车,out表示出站的火车
stack<int> in;
vector<int> out;
int n, cnt = 20;

void dfs(int num){
	//当输出达到20个或者火车全部出站,返回,结束查找
    if(cnt == 0) return;
    if(out.size() == n){
        for(int i=0; i<out.size(); i++) cout << out[i];
        cout << endl;
        cnt--;
    }
    
    //出站
    if(in.size()){
        out.push_back(in.top());
        in.pop();
        dfs(num);
        in.push(out.back());
        out.pop_back();  
    }
    
    //入站
    if(num <= n){
        in.push(num++);
        dfs(num--);
        in.pop();
    }
}

int main(){
    cin >> n;
    dfs(1);
    
    return 0;
}
火车进出站问题

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

将火车进站看作“+”,将火车出站看作“-”,那么对于某个进出站过程可以用加减号来表示,例如序列1、2、3、4,出栈成3、4、2、1,那么可以表示成“++±±–”。则排列方式可以间接由加减号的排序映射,对于“+”、“-”的排序限制条件就是在任意位置之前,“+”的总数一定要多于“-”的总数,这样才能保证不会要求空栈弹出。如果把“+”看作横轴,“-”看作纵轴,可以表示成如下图。

在这里插入图片描述
绿色的点代表我们要到达的点,对于一个“+±+±+±-±–”可以表示成蓝色的线。
在这里插入图片描述
而对于上图红色的线,是“++±—+++±–”,是不能满足任意位置“+”总数多于“-”总数的要求,因此我们的序列不能穿过黄色的线。
在这里插入图片描述
而对于任意穿过黄色的路径,必将到达上图的红线,对于一个非法路径,我们总可以找到关于红线对称的路径到达(n,n+1)点,即紫色的点,因此对于非法路径数就是 ( 2 n n − 1 ) \binom{2n}{n-1} (n12n),到达绿色点的可能路径数是 ( 2 n n ) \binom{2n}{n} (n2n),因此合法路径数就是 ( 2 n n ) − ( 2 n n − 1 ) = 1 n + 1 ( 2 n n ) \binom{2n}{n}-\binom{2n}{n-1}=\frac{1}{n+1}\binom{2n}{n} (n2n)(n12n)=n+11(n2n)
因为该题数据较大,因此采用高精度乘法,为了加快运算,我们将每个数分解质因子乘积的形式。

#include <iostream>
#include <vector>

using namespace std;

//flag是标志位,用来筛出质因子,primes中存放所有小于2n的质因子,mi存放每个质因子的次数
const int N = 120010;
int n, cnt, flag[N], primes[N], mi[N];
vector<long long> res(1, 1);

//查找质因子,对于找到的质因子,我们将其倍数置位,不再访问
void get_primes(){
    for(int i=2; i<2*n; i++){
        if(!flag[i]){
            primes[cnt++] = i;
            for(int j=i; j<2*n; j+=i){
                flag[j] = 1;
            }
        }
    }
}

//得到num阶乘中质因子prime的次数
int get(int num, int prime){
    int ans = 0;
    while(num / prime){
        ans += num / prime;
        num /= prime;
    }
    return ans;
}

//高精度乘法
void multi(int num, int prime){
    while(num--){
        long long t = 0;
        for(int i=res.size() - 1; i>=0; i--){
            t += res[i] * prime;
            res[i] = t % 1000000000;
            t /= 1000000000;
        }
        while(t!=0){
            res.insert(res.begin(), t % 1000000000);
            t /= 1000000000;
        }
    }    
}

int main(){
    cin >> n;
    get_primes();
    int den = n + 1;
    for(int i=0; i<cnt; i++){
    	//分别求出2n和n的质因子次数,并相减表示除法
        mi[primes[i]] = get(2 * n, primes[i]) - 2 * get(n, primes[i]);
        //减去n+1中质因子的次数
        while(den % primes[i] == 0){
            mi[primes[i]]--;
            den /= primes[i];
        }
    }

	//高精度累乘
    for(int i=0; i<cnt; i++)
        if(mi[primes[i]]) multi(mi[primes[i]], primes[i]);
    
    
    printf("%d", res[0]);
    for(int i=1; i<res.size(); i++) printf("%09d", res[i]);
    
    return 0;
}
直方图中最大的矩形

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

我们可以遍历矩形,找到该矩形高度下的左边界和右边界,即可求出该高度下面积的最大值。对于边界的求解可以利用栈,对于栈顶矩形的高度大于当前矩形的高度,我们就将栈顶矩形弹出,直至找到第一个小于当前矩形高度的矩形,以此作为左边界,并将当前举行压入栈中,使得栈内矩形高度始终是递增趋势,对于右边界同理。通过栈便可以在 O ( n ) O(n) O(n)时间内求出答案。

#include <iostream>

using namespace std;

const int N = 100010;
//stack是要维护的栈,存放矩阵的索引
int n, stack[N];

//每个矩阵的信息,包括高度、左边界和右边界
struct matix{
    int height, l, r;
}num[N];

int main(){
	//边界条件
    num[0].height = -1;
    //n表示每组矩阵个数,当n为0则结束
    while(cin >> n, n){
   		//输入矩阵
        for(int i=1; i<=n; i++){
            cin >> num[i].height;
        }
        //id是指向栈顶元素
        int id = 0;
        for(int i=1; i<=n; i++){
        	//找到第一个小于当前矩阵高度的栈顶
            while(num[i].height <= num[stack[id]].height){
                id--;
            }
            //记录左边界
            num[i].l = stack[id];
            //将当前矩阵压栈
            stack[++id] = i;
        }
        //逆序找右边界,同上
        id = 0;
        for(int i=n; i>0; i--){
            while(num[i].height <= num[stack[id]].height){
                id--;
            }
            num[i].r = stack[id]? stack[id]-1: n-stack[id];
            stack[++id] = i;
        }
        //遍历查找最大矩形面积
        long long res = 0, area;
        for(int i=1; i<=n; i++){
            area = num[i].height * 1ll * (num[i].r - num[i]. l);
            res = max(res, area);
        }
        cout << res << endl;
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值