考研算法(二) 栈

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 ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

思想:

栈的作用有括号匹配,表达式匹配等等。基本上都是跟括号有关。括号的匹配就是左右对应(大小,内容),具有这样的特点常用栈来处理。

   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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值