二、栈
1.最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
思想:
栈设计的模式是后进先出。我们只能操作栈顶,因此栈顶必须是最小值,栈的模式不能改变,所以我们需要另开一个最小栈。我们将栈初始化为空,然后往栈里插入元素,然后我们同时往最小栈插入一个最小值,即使当前元素不是最小值也要插入,弹出时也最小栈同时弹出。非常巧妙。
stack<int> s,mins;//mins栈同步存储当前栈内元素的最小值
MinStack() {
}
void push(int x) {
s.push(x);
if(mins.empty()) mins.push(x);
else mins.push(min(mins.top(),x));
}
void pop() {
s.pop();
mins.pop();
}
int top() {
return s.top();
}
int getMin() {
return mins.top();
}
2.有效的括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
思想:
栈的作用有括号匹配,表达式匹配等等。基本上都是跟括号有关。括号的匹配就是左右对应(大小,内容),具有这样的特点常用栈来处理。
bool isValid(string s) {
stack<char> stk;
for(auto v : s){
if(v=='('||v=='{'||v=='[') stk.push(v);//是左括号则直接入栈
else if(stk.empty()) return false;//只有右括号没有左括号时显然不匹配
else{
int x = stk.top();
stk.pop();
if(v==')'&&x!='('||v=='}'&&x!='{'||v==']'&&x!='[') return false;//不匹配情况
}
}
return stk.empty();//完整的匹配最后栈应该为空
}
3.栈序列合法性
给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。
思想:
考研的卷子老师喜欢出这样的选择题,现在是个编程题了。每次入栈的时候判断一下能不能出栈,把能出的都出栈,如果最后栈为空则合法。
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> s;
int n = pushed.size();
for(int i = 0,j=0; i < n; i++){//模拟进栈
s.push(pushed[i]);//先直接进栈
//然后根据出栈序列决定是否出栈
while(!s.empty()&&j<n&&s.top()==popped[j]) s.pop(),j++;//出栈条件
}
return s.empty() ? true:false;//若合法,则此时栈一定是空的
}
4.模拟栈
实现一个栈,栈初始为空,支持四种操作:
push x – 向栈顶插入一个数 x;
pop – 从栈顶弹出一个数;
empty – 判断栈是否为空;
query – 查询栈顶元素。
现在要对栈进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。
思想:
数组模拟栈其实就是维护一下栈顶的位置,数组模拟队列就是维护一下队头和队尾的位置。
所有的头文件和变量的定义及输入输出都可以忽略。
模板
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{
}
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int stk[N],tt;
int m;
int main(){
cin>>m;
string op;
while(m--){
cin>>op;
if(op=="push"){
int x;
cin>>x;
stk[++tt]=x;
}else if(op=="pop") tt--;
else if(op=="empty") cout<<(tt?"NO":"YES")<<endl;
else cout<<stk[tt]<<endl;
}
return 0;
}
5.单调栈
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
思想:
维护一个单调递增的栈,遍历数组,每次将大于等于将要入栈元素的元素弹出,这样就能找到第一个比他小的元素。
模板
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int stk[N];
int main(){
cin>>n;
int tt=0;
while(n--){
int x;
cin>>x;
while(tt&&stk[tt]>=x) tt--;
if(!tt) cout<<-1<<" ";
else cout<<stk[tt]<<" ";
stk[++tt]=x;
}
return 0;
}
6.逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
思想:
int getv(string &s){
int ans = 0;
int st=0;
if(s[0]=='-') st=1;
for(int i = st; i < s.size(); i++) ans = ans*10+s[i]-'0';
return st==1?-ans:ans;
}
long long calc(int a, int b, char op){
if(op=='+') return a+b;
else if(op=='-') return a-b;
else if(op=='*') return (long long)a*b;
else return a/b;
}
int evalRPN(vector<string>& t) {
stack<int> stk;
for(auto s : t){
if(s[0]>='0'&&s[0]<='9'||s[0]=='-'&&s.size()>1) stk.push(getv(s));//读入数字 负数和减号需要特判
else {//是运算符,则从栈顶弹出两个操作数 进行运算
int b = stk.top();
stk.pop();
int a = stk.top();
stk.pop();
stk.push(calc(a,b,s[0]));
}
}
return stk.top();
}
7.表达式计算
计算表达式
例如:(2+2)^(3+1)
思想:
用双指针读取每一个数字放入数字栈,(放入符号栈,)不入栈直接计算,直到左括号出栈。用哈希表存储优先级,优先级高的先算。
#include<bits/stdc++.h>
using namespace std;
stack<char> ops;//运算符栈
stack<int> nums;//运算数栈
int quick_mi(int a, int b){//快速幂
int t = a,ans = 1;
while(b){
if(b&1) ans*=t;
t = t*t;
b>>=1;
}
return ans;
}
void calc(){//执行一次计算
int b = nums.top();
nums.pop();
int a = nums.top();
nums.pop();
char c = ops.top();
ops.pop();
int d;//结果
if(c=='+') d = a + b;
else if(c=='-') d = a - b;
else if(c=='*') d = a * b;
else if(c=='/') d = a / b;
else d = quick_mi(a,b);
nums.push(d);
}
int main(){
unordered_map<char,int> pr{{'+',1},{'-',1},{'*',2},{'/',2},{'^',3}};
string str,left;
ios::sync_with_stdio(false);
cin.tie(0);
cin>>str;
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,ta = 0;
while(str[j]>='0'&&str[j]<='9') ta = ta*10+str[j]-'0',j++;//获得该数的值
i = j-1;
nums.push(ta);
}
else if(str[i]=='-'&&i&&!(str[i-1]>='0'&&str[i-1]<='9')&&str[i-1]!=')'){//读取负数 负号的判定,负号前如果是数字,则是减号,反之即可
int j = i+1,ta = 0;
while(str[j]>='0'&&str[j]<='9') ta = ta*10+str[j]-'0',j++;//获得该数的值
i = j-1;
nums.push(-ta);
}
else if(str[i]=='(') ops.push(str[i]);//左括号直接进
else if(str[i]==')'){//括号内的此时一定是优先级递增 可以把括号里的都算了
while(ops.top()!='(') calc();
ops.pop();
}
else{
while(ops.top()!='('&&pr[ops.top()]>=pr[str[i]]){
calc();
}
ops.push(str[i]);
}
while(ops.size()) calc();
cout<<nums.top();
return 0;
}