栈 的应用

栈的应用


先来道开胃菜

acwing150 括号画家

题目描述

达达是一名漫画家,她有一个奇特的爱好,就是在纸上画括号。

这一天,刚刚起床的达达画了一排括号序列,其中包含小括号 ( )、中括号 [ ] 和大括号 { },总长度为 N。

这排随意绘制的括号序列显得杂乱无章,于是达达定义了什么样的括号序列是美观的:

空的括号序列是美观的;
若括号序列 A 是美观的,则括号序列 ( A )、[ A ]、{ A } 也是美观的。
若括号序列 A、B 都是美观的,则括号序列 AB 也是美观的。
例如 [ ( ){ } ] ( ) 是美观的括号序列,而 ) ( { ) [ } ] ( 则不是。

现在达达想在她绘制的括号序列中,找出其中连续的一段,满足这段子串是美观的,并且长度尽量大。

你能帮帮她吗?

输入格式

输入一行由括号组成的字符串。

输出格式

输出一个整数,表示最长的美观的子段的长度。

数据范围

字符串长度不超过 105。

输入样例
({({(({()}})}{())})})[){{{([)()((()]]}])[{)]}{[}{)
输出样例
4

简溪

这题是栈的应用,或是说栈的性质。

如何找出一个括号对?栈! 如果是左括号 ( [ { 就入栈,如果是右括号 ) ] } 并且栈顶元素为其相应的左括号,就出栈左括号;否则入栈右括号。我们来手搓一遍样例:

  • ( { ( { ( ( { ( 全都是左括号,直接入栈。
  • 读入右括号 ) 且栈顶元素为 ( ,出栈,序列为 ( { ( { ( ( {
  • 读入右括号 } 且栈顶元素为 { ,出栈,序列为 ( { ( { ( (
  • 读入右括号 } ,而栈顶元素为 ( ,入栈 }

对于一些细节问题与疑问,我们先来看代码

先上代码

#include<iostream>
#include<stack>
#include<cstring>
using namespace std;

#define N 100010

int ans;
char c[N];
stack<int> stk;

int main(){
	scanf("%s",c);
	for(int i=0;i<strlen(c);i++){
		char s=c[i];
		if(s==')'&&stk.size()&&c[stk.top()]=='(') 
			stk.pop();
		else if(s=='}'&&stk.size()&&c[stk.top()]=='{') 
			stk.pop();
		else if(s==']'&&stk.size()&&c[stk.top()]=='[') 
			stk.pop();
		else 
			stk.push(i);
		if(stk.size()!=0) 
			ans=max(ans,i-stk.top());
		else 
			ans=max(ans,i+1);
	}
	
	cout<<ans<<endl;
	
	return 0;
}

这段代码有几个细节要注意:

  1. 他的栈的类型为int,所以对应的是字符串 c 中的下标
  2. 因为用的是int,所以计算最大序列的方式是 i-stk.top() ,i为当前字符串下标,stk.top()是栈顶的字符串下标。比如 i = 6 时,前面的括号由于相互对应而出栈, stk.top() 为 3 ,res则为 3 。
  3. 对于栈的判空,当栈为空,无法返回栈顶元素,读入右括号时,就直接入栈;当栈无法返回栈顶元素,res就为 i + 1。

第二道

151. 表达式计算4 - AcWing题库

受不了了,又调好久,感觉是表达式求值的究极版。

碱洗

先不考虑括号,只考虑运算符。

首先,我们都知道。一个表达式可以用二叉树表达,也就是一个中缀表达式的中序遍历。要用一个迭代的方式来运算递归的式子,用栈是很方便的。说了这么多,也就是说,像第一道括号配对和表达式求值这样的题目,就用栈来做。

接下来,我们来思考一下如何才能让按照运算符有序地计算?因为优先级只有两种情况:1.比前面优先 2.前面优先。那么我们只要让优先级为单调递增,运算起来就会十分方便。实现方法就是当读入运算符时,与栈顶的运算符进行比较,如果优先级低于或等于栈顶的运算符,那就直接运算。

接下来再来考虑括号的情况。既然括号内的式子必须先计算,那么当遇到右括号,就一直进行向前的运算。因为此时的优先级为递增,所以从后往前算没毛病,直到遇到左括号停止。

小技巧:如何让栈内只剩下答案:可以在最右边额外加上一个‘ )’那么就可以将括号内剩余的值算完

最后,我们只剩下一些特殊情况要处理:

  1. 负号与减号混淆。负号如果是在式子首,即str[ 0 ]=’ - ',那就直接判定接下来的数字为负数。如果不是式子首,就要判断前面不是数字,不是‘ )’,那就是负号。否则是减号。
  2. 式子可能会有很多个右括号,可以在式子前加上许多 ( 来避免错误。左括号也没有什么影响。(题目坑人)

更多小细节可以细看代码,虽然看起来很长,其实内容并不复杂。

代码

#include<iostream>
#include<stack>
#include<cstring>
using namespace std;

stack<int> nums;
stack<char> ops;

int qmi(int a,int k){
    int res=1;
    while (k--) res*=a;
    return res;
}

void cal(){
    int a=nums.top();nums.pop();
    int b=nums.top();nums.pop();
    char c=ops.top();ops.pop();
    int d;
    if (c=='+')d=b+a;
    else if (c=='-') d=b-a;
    else if (c=='*') d=b*a;
    else if (c=='/') d=b/a;
    else d=qmi(b,a);

    nums.push(d);
}

int main(){
	string str;
	cin>>str;
	if(str[0]=='-')
		str='0'+str;
	string left;
	for(int i=0;i<str.size();i++)
		left+='(';
	str=left+str+')';
	for(int i=0;i<str.size();i++){
		if(str[i]>='0'&&str[i]<='9'){
			int j=i,t=0;
			while(str[j]>='0'&&str[j]<='9'){
				t=t*10+str[j]-'0';
				j++;
			}
			i=j-1;
			nums.push(t);
		}
		else{
			char c=str[i];
			if (c=='(') ops.push(c);
			else if(c=='+'||c=='-'){
				 if (c =='-'&&i &&!(str[i - 1]>='0'&&str[i - 1]<='9')&&str[i-1]!=')'){
                    if (str[i + 1]=='(') {  // 将-(...)变成-1 * (...)
                        nums.push(-1);
                        ops.push('*');
                    }
                    else{
                        int j=i+1,t=0;
                        while (str[j]>='0'&&str[j]<'9'){
                            t=t*10+str[j]-'0';
                            j++ ;
                        }
                        nums.push(-t);
                        i=j-1;
                    }
                }
                else{
                    while(ops.top()!='(')
                        cal();
                    ops.push(c);
                }
			}
			else if (c=='*'||c=='/'){
                while (ops.top()=='*'||ops.top()=='/'||ops.top()=='^') cal();
                ops.push(c);
            }
            else if (c == '^'){
                while(ops.top()=='^')cal();
                ops.push(c);
            }
            else if (c == ')'){
                while(ops.top()!='(') cal();
                ops.pop();
            }
		}
	}
	cout<<nums.top();
	return 0;
}

第三道

题目描述

152. 城市游戏 - AcWing题库

有一天,小猫 rainbow 和 freda 来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。

这片土地被分成 N×M 个格子,每个格子里写着 R 或者 FR 代表这块土地被赐予了 rainbow,F 代表这块土地被赐予了 freda。

现在 freda 要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着 F 并且面积最大。

但是 rainbow 和 freda 的 OI 水平都弱爆了,找不出这块土地,而蓝兔也想看 freda 卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为 S,它们将给你 3×S两银子。

输入格式

第一行包括两个整数 N,M,表示矩形土地有 N 行 M 列。

接下来 N 行,每行 M 个用空格隔开的字符 FR,描述了矩形土地。

每行末尾没有多余空格。

输出格式

输出一个整数,表示你能得到多少银子,即(3×最大 F 矩形土地面积)的值。

数据范围

1≤N,M≤1000

输入样例:
5 6
R F F F F F
F F F F F F
R R R F F F
F F F F F F
F F F F F F
输出样例:
45

分析

这题的做法与下面这题做法相似,只不过换成了字符串。只要用递推算出该点上面的连续矩形长,再遍历每一个点,时间复杂度为O(M*N)。

131. 直方图中最大的矩形 - AcWing题库

首先明确一点:一个矩形的延伸只会被比它矮的矩形卡住。

在这里插入图片描述

如上图,来看第 3 个矩形。以这个矩形为基础,向左延伸,然而 2 比它矮,所以不算入;向右延伸,4,5,6 比 3 高,可以截取与 3 等高部分算入面积。

所以接下来就很明确:要分别寻找矩形 i 左,右最近的比该矩形的矩形,存入l [ i ], r [ i ]。

先来看代码

#include<iostream>
#include<algorithm>
using namespace std;
	
#define N 1010
	
int n,m,res;
int h[N][N];//储存高度
char g[N][N];
int q[N],l[N],r[N];//q为单调栈(存储的是下标),l为左边最近
	
void get(int a[],int l[]){
	int tt=0;//tt为栈顶下标
	a[0]=-1;
	for(int i=1;i<=m;i++){
		while(a[q[tt]]>=a[i]) tt--;
		l[i]=q[tt]+1;
		q[++tt]=i;
	}
}
	
int work(int a[]){
	get(a,l);
	reverse(a+1,a+1+m);
	get(a,r);
	reverse(a+1,a+1+m);
	int res=0;
	for(int i=1;i<=m;i++){
		int left=l[i];
		int right=m+1-r[m+1-i];
		res=max(res,a[i]*(right-left+1));
	}
	return res;
}
	
int main(){
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			char c;
			cin>>c;
			g[i][j]=c;
			if( c=='F') h[i][j]=h[i-1][j]+1;
			else h[i][j]=0;
		}
	}
	for(int i=1;i<=n;i++){
		res=max(res,work(h[i]));
	}
	cout<<res*3<<endl;
	return 0;
}

  • reverse函数是将数组前后倒过来,为了方便一点求矩形 i 左,右最近的比该矩形的矩形。

但是不方便的一点是:因为求 r 时倒过来了,所以下标也是倒过来的,那么right要转换一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值