栈的应用

栈的应用

acwing150 括号画家

题目描述

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

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

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

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

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

你能帮帮她吗?

输入格式

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

输出格式

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

数据范围

字符串长度不超过 105。

输入样例

1
({({(({()}})}{())})})[){{{([)()((()]]}])[{)]}{[}{)

输出样例

1
4

分析

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

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

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

先上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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. 式子可能会有很多个右括号,可以在式子前加上许多 ( 来避免错误。左括号也没有什么影响。(题目坑人)

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

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#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

输入样例:

1
2
3
4
5
6
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

输出样例:

1
45

分析

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

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

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

在这里插入图片描述

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

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

先来看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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;
}

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