【优选算法】栈 {后缀表达式求值;中缀转后缀表达式;中缀表达式求值}

news2024/11/24 19:40:58

一、经验总结

何时使用栈结构解题?

  1. 做过相似的使用栈结构解得的题目
  2. 嵌套处理:在从前向后处理的过程中,由于之后内容的不确定性而导致当前操作不能贸然进行,需要先进行保存,直到遇到区间结束标志(如’)')此时才能处理最近区间的内容。从前往后存,从后往前取,正好是栈后进先出的特点。

二、相关编程题

2.1 删除字符串中的所有相邻重复项

题目链接

1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:
    string removeDuplicates(string s) {
        string ret; //用数组模拟栈结构
        for(auto ch : s)
        {
            if(!ret.empty() && ch == ret.back())
                ret.pop_back();
            else
                ret.push_back(ch);
        }
        return ret;
    }
};

2.2 比较含退格的字符串

题目链接

844. 比较含退格的字符串 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

同上,略

编写代码

class Solution {
public:
    bool backspaceCompare(string s, string t) {
        return ChangeStr(s) == ChangeStr(t);
    }

    string ChangeStr(const string& str)
    {
        string ret;
        for(auto ch : str)
        {
            if(ch == '#')
            {
                if(!ret.empty()) ret.pop_back();
            } 
            else
                ret+=ch;
        }
        return ret;
    }
};

2.3 基本计算器 II(无括号)

题目链接

227. 基本计算器 II - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:
    int calculate(string s) {
        stack<int> st;
        char op = '+';
        int i = 0;
        while(i < s.size())
        {
            if (s[i] == ' ') ++i;
            else if (s[i] >= '0' && s[i] <= '9') 
            {
                int num = 0;
                while (i < s.size() && s[i] >= '0' && s[i] <= '9') 
                {
                    num = num * 10 + (s[i]-'0');
                    ++i;
                }
                switch (op) 
                {
                case '+':
                    st.push(num);
                    break;
                case '-':
                    st.push(-num);
                    break;
                case '*':
                    st.top()*=num;
                    break;
                case '/':
                    st.top()/=num;
                    break;
                }
            }
            else op = s[i++];
        }

        int ret = 0;
        while(!st.empty())
        {
            ret += st.top();
            st.pop();
        }
        return ret;
    }
};

2.4 逆波兰表达式求值

题目链接

150. 逆波兰表达式求值 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理
后缀表达式求值
后缀表达式,也称为逆波兰表达式,是一种没有操作符优先级的表达式(也没有左右括号),因此求值过程相对直接。求值过程通常涉及使用一个栈来辅助存储操作数,并按照以下步骤进行:

  1. 遍历后缀表达式中的每个元素。
  2. 如果遇到操作数(通常是数字),则将其压入栈中。
  3. 如果遇到操作符,则从栈中弹出两个操作数进行计算,并将结果压回栈中。
  4. 这个过程持续直到后缀表达式中的所有元素都被处理,最终栈中剩下的就是表达式的计算结果。

逆波兰 - 下(后缀表达式计算结果)_哔哩哔哩_bilibili

编写代码

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> nums; //使用一个栈来辅助存储操作数
        for(auto &str : tokens)
        {
            if(IsNum(str))
            {
                nums.push(stoi(str));
            }
            else
            {
                int right = nums.top(); //后进先出,右操作数先出栈
                nums.pop();
                int left = nums.top();
                nums.pop();
                switch(str[0])
                {
                    case '+':
                        nums.push(left+right);
                    break;
                    case '-':
                        nums.push(left-right);
                    break;
                    case '*':
                        nums.push(left*right);
                    break;
                    case '/':
                        nums.push(left/right);
                    break;
                }
            }
        }
        return nums.top();
    }

    bool IsNum(const string &str)
    {
        if(str[0] == '-')
        {
            if(str.size() > 1) return true;
        }
        else if(str[0] >= '0' && str[0] <= '9')
        {
            return true;
        }
        return false;
    }
};

2.5 中缀表达式求值(有括号)

题目链接

表达式求值_牛客题霸_牛客网 (nowcoder.com)

题目描述

在这里插入图片描述

算法原理

解法一:中缀表达式转后缀表达式,再运算

在中缀变后缀时,操作数的顺序不会发生变化,只有运算符的顺序可能发生变化。同时又没有括号。所以在转换的过程中,只要碰到操作数,可以直接输出,而遇到运算符和括号进行相应的处理即可。

转换原则如下:

1.从左到右读取一个中序表达式。

2.若读取的是操作数,则直接输出。

3.若读取的是运算符,分三种情况。

  1. 该运算符为左括号( ,则直接存入堆栈。 (等待右括号出现;提高优先级:隔断左括号之前的运算符优先级比较)

  2. 该运算符为右括号),则输出堆栈中的运算符,直到取出左括号为止。 (提高优先级:优先处理括号内的运算)

  3. 该运算符为非括号运算符,则与堆栈顶端的运算符做优先权比较:

    • 如果栈为空或当前运算符优先级高于栈顶运算符,则直接入栈。(不能确定后面的运算符优先级是否更高,入栈等待)
    • 如果当前运算符优先级低于等于栈顶运算符,则弹出栈顶运算符并输出,直到当前运算符优先级大于栈顶运算符或者栈空为止,然后将当前运算符入栈。(能够确定栈顶运算符的优先级比后面的运算符(当前)优先级更高,出栈运算)

4.当表达式已经读取完成,而堆栈中尚有运算符时,则依次序取出运算符,直到堆栈为空,由此得到的结果就是中缀表达式转换成的后缀表达式。

5.后缀表达式求值参考上一题。

逆波兰 - 上(中缀表达式 转 后缀表达式)_哔哩哔哩_bilibili

解法二:数字栈和算符栈

中缀表达式求值通常涉及使用栈来处理运算符和操作数。以下是中缀表达式求值的基本步骤:

  1. 定义两个栈,一个用于存储操作数,另一个用于存储运算符。

  2. 遍历中缀表达式的每个字符,如果遇到操作数(数字),则将其压入操作数栈。

  3. 如果遇到运算符,则将其与运算符栈的栈顶元素比较优先级:

    1. 如果运算符的优先级高于栈顶元素的优先级或运算符栈为空,将其压入运算符栈。(不能确定后面的运算符优先级是否更高,入栈等待)
    2. 如果运算符的优先级低于或者等于栈顶元素的优先级,则从操作数栈弹出两个操作数进行计算,并将结果压入操作数栈,同时将运算符栈中优先级低于或等于当前运算符的元素依次弹出并处理,直到遇到一个优先级高于当前运算符的元素或栈为空,再将运算符压入运算符栈。(能够确定栈顶运算符的优先级更高,出栈运算)
  4. 如果遇到左括号“(”,则直接将其压入运算符栈。(提高优先级:隔断左括号之前的运算符优先级比较;等待右括号出现)

  5. 如果遇到右括号“)”,则从运算符栈弹出并处理运算符,直到遇到左括号“(”为止。(提高优先级:优先处理括号内的运算)

  6. 遍历完成后,如果运算符栈中还有元素,则从操作数栈弹出两个操作数进行计算,并将结果压回操作数栈。

  7. 最后,操作数栈中剩下的元素(如果有)就是表达式的求值结果。

这种方法的关键在于正确处理运算符的优先级和括号,其实底层原理与解法一相同。

中缀表达式的计算_哔哩哔哩_bilibili

编写代码

//解法一:中缀表达式转后缀表达式,再运算
#include <cmath>
class Solution {
public:
    int solve(string s) {
        stack<char> ops;
        unordered_map<char, int> op_level = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; //运算符的优先级
        vector<string> tokens; //存储后缀表达式
        //中缀转后缀
        int i = 0;
        while(i < s.size())
        {
            if(IsNum(s[i]))
            {
                string num;
                while(i < s.size() && IsNum(s[i]))
                {
                    num += s[i++];
                }
                tokens.push_back(num);
            }
            else if (s[i] == '(') 
            {
                ops.push(s[i++]);
            } 
            else if (s[i] == ')') 
            {
                while(ops.top() != '(')
                {
                    tokens.push_back(string(1, ops.top()));
                    ops.pop();
                }
                ops.pop();
                ++i;
            }
            else 
            {
                while (!ops.empty() && ops.top() != '(' && op_level[s[i]] <= op_level[ops.top()]) 
                {
                    tokens.push_back(string(1, ops.top()));
                    ops.pop();
                }
                ops.push(s[i++]);
            } 
        }

        while(!ops.empty())
        {
            tokens.push_back(string(1, ops.top()));
            ops.pop();
        }

        //后缀表达式求值
        return evalRPN(tokens); //这里复用上一题的代码,直接复制过来使用就行
    }

    bool IsNum(char ch)
    {
        return ch >= '0' && ch <= '9';
    }
};

//解法二:数字栈和算符栈
class Solution {
    stack<char> op_st; //算符栈
    stack<int> num_st; //数字栈
    unordered_map<char, int> op_level = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; //运算符的优先级
  public:
    int solve(string s) {
        int i = 0;
        while (i < s.size()) {
            if (IsNum(s[i])) 
            {
                int num = 0;
                while (i < s.size() && IsNum(s[i]))
                    num = num * 10 + (s[i++] - '0');
                num_st.push(num);
            } 
            else if (s[i] == '(') 
            {
                op_st.push(s[i++]);
            } 
            else if (s[i] == ')') 
            {
                while (op_st.top() != '(') calc();
                op_st.pop();
                ++i;
            } 
            else 
            {
                while (!op_st.empty() && op_st.top() != '(' && op_level[s[i]] <= op_level[op_st.top()]) calc();
                op_st.push(s[i++]);
            }
        }

        while(!op_st.empty()) calc();
        return num_st.top();
    }

    bool IsNum(char ch) {
        return ch >= '0' && ch <= '9';
    }
	
    //calc的工作是从栈中取出2个操作数和1个操作符进行运算,并将结果压入数字栈
    void calc() {
        char op = op_st.top();
        op_st.pop();
        int right = num_st.top();
        num_st.pop();
        int left = num_st.top();
        num_st.pop();
        int ret = 0;
        switch (op) {
            case '+':
                ret = left + right;
                break;
            case '-':
                ret = left - right;
                break;
            case '*':
                ret = left * right;
                break;
            case '/':
                ret = left / right;
                break;
        }
        num_st.push(ret);
    }
};

2.6 字符串解码

题目链接

394. 字符串解码 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:
    string decodeString(string s) {
        stack<string> str_st;
        stack<int> num_st;
        str_st.push("");
        int i = 0;
        while(i < s.size())
        {
            if(IsNum(s[i]))
            {
                int num = 0;
                while(i < s.size() && IsNum(s[i]))
                {
                    num = num*10+(s[i]-'0');
                    ++i;
                }
                num_st.push(num);
            }
            if(s[i] == '[')
            {
                ++i;
                string tmp;
                while(i < s.size() && IsLetter(s[i]))
                {
                    tmp += s[i];
                    ++i;
                }
                str_st.push(tmp);
            }
            if(s[i] == ']')
            {
                string top = str_st.top();
                str_st.pop();
                int k = num_st.top();
                num_st.pop();
                while(k--)  str_st.top()+=top;
                ++i;
            }
            if(IsLetter(s[i]))
            {
                str_st.top()+=s[i];
                ++i;
            }
        }
        return str_st.top();
    }

    bool IsNum(char ch)
    {
        return ch >= '0' && ch <='9';
    }
    
    bool IsLetter(char ch)
    {
        return ch >= 'a' && ch <= 'z';
    }
};

2.7 验证栈序列

题目链接

946. 验证栈序列 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> st;
        int j = 0;
        for(int i = 0; i < pushed.size(); ++i)
        {
            st.push(pushed[i]);
            while(!st.empty() && st.top() == popped[j])
            {
                st.pop();
                ++j;
            }
        }
        return j == popped.size();
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1806913.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MLU370-M8 chattts-ui快速出击

目录 一、paas平台环境选择二、代码环境准备1.代码下载2.环境安装modelsopetransformersaccelerate 3.常规pip安装4.代码修改4.代码修改 三.算法启动 一、paas平台环境选择 驱动选择&#xff1a;5.10.22及以上 镜像选择&#xff1a;pytorch2.1 二、代码环境准备 1.代码下载…

HC-SR505人体感应灯

1硬件 1.1硬件组成 1.正点原子探索者开发板 2 HC-SR505迷你小型人体感应模块 3 继电器&#xff0b;5V小灯 HC-SR505迷你小型人体感应模块介绍 1.2 硬件连接 1.HC-SR505&#xff08;连接在PE0&#xff09; 2.继电器&#xff08;连接在PE1&#xff09; 2.主要代码 int ma…

【Stable Diffusion】(基础篇一)—— Stable Diffusion的安装

本系列笔记主要参考B站nenly同学的视频教程&#xff0c;传送门&#xff1a;B站第一套系统的AI绘画课&#xff01;零基础学会Stable Diffusion&#xff0c;这绝对是你看过的最容易上手的AI绘画教程 | SD WebUI 保姆级攻略_哔哩哔哩_bilibili **Stable Diffusion&#xff08;简称…

SpringCloud Gateway中Filters详细说明

前面 https://blog.csdn.net/J080624/article/details/139494909 我们研究了GateWay中各种路由断言的使用。SpringCloud GateWay 还提供了各种过滤器用来对请求和响应进行处理。 官网地址&#xff1a;SpringCloud Gateway Filter 【1】GatewayFilter Factories 路由过滤器允…

前端修改接口返回测试工具 Inssman使用教程

之前用的requestly现在要登录才能用了&#xff0c;然后我又登录不上去&#xff0c;同事又推荐了个谷歌插件&#xff0c;试了下&#xff0c;挺好用&#xff0c;还不用登录&#xff0c;用法和之前差不多 下载网站&#xff1a;https://chromewebstore.google.com/detail/inssman-…

【网络安全】【深度学习】【入侵检测】SDN模拟网络入侵攻击并检测,实时检测,深度学习

文章目录 1. 前言2. Mininet 和 Ryu 的区别2.1 Mininet2.2 Ryu2.3 总结 3. 模拟攻击3.1 环境准备3.2 创建 Mininet 网络拓扑3.2 启动 Ryu 控制器3.3 模拟网络攻击3.4 捕获流量 4. 实时异常检测4.1 在 Ryu 控制器中4.2 在 h2 机器上的实验结果4.3 深度学习模型部署上h2机器 帮助…

OrangePi Kunpeng Pro深度评测:性能与体验的完美融合

文章目录 一、引言二、硬件开箱与介绍1.硬件清单2.硬件介绍 三、软件介绍四、性能测试1. 功率测试2. cpu测试2.1 单线程cpu测试2.2 多线程cpu测试 五、实际开发体验1. 搭建API服务器2. ONNX推理测试3. 在线推理平台 五、测评总结1. 能与硬件配置2. 系统与软件3. 实际开发体验个…

[Kubernetes] 容器运行时 Container Runtime

文章目录 1.容器运行时(Container Runtime)2.容器运行时接口3.容器运行时层级4.容器运行时比较5.强隔离容器6.K8S为何难以实现真正的多租户 1.容器运行时(Container Runtime) Container Runtime 是运行于 k8s 集群每个节点中&#xff0c;负责容器的整个生命周期。Docker 就目前…

Redis的删除策略与内存淘汰

文章目录 删除策略设置过期时间的常用命令过期删除策略 内存淘汰相关设置LRU算法LFU 总结 在redis使用过程中&#xff0c;常常遇到以下问题&#xff1a; 如何设置Redis键的过期时间&#xff1f;设置完一个键的过期时间后&#xff0c;到了这个时间&#xff0c;这个键还能获取到么…

Foxmail邮箱的使用方法和功能最全介绍

Foxmail邮箱是我们办公邮箱中比较有代表性和使用性的一款邮箱软件&#xff0c;今天笔者为大家介绍一下Foxmail邮箱的功能和使用方法。 1、首先我们从安装Foxmail邮箱开始 2、点击安装等待安装成功 3、双击打开 &#xff0c;出现邮箱设置界面输入我们的账号密码&#xff0c;点击…

ESP32:往MicroPython集成PCNT以支持硬件正交编码器

背景 官方发布的1.23依然没有在ESP32中集成PCNT功能。考虑到硬件的PCNT模块可以提供4倍的编码精度&#xff08;对比使用PIn IRQ&#xff09;&#xff0c;还能提供硬件去毛刺。 还是自己集成一下吧。 实际上Github上早在2022年1月的时候就已经有人建议了将PCNT加入正式版本的功…

家庭电脑私网如何访问阿里云服务器的指定端口

这里我们以在阿里云服务器上部署一个redis server 服务&#xff0c;对外开放6379端口为例子&#xff0c;其他端口类似。 1.获取当前电脑主机对应的公网IP, 可以https://tool.lu/ip/通过这个网站拿到。 2.阿里云服务器控制台设置防火墙&#xff0c;如下图所示&#xff0c;直接添…

【leetcode22-36】链表

160.相交链表 【等比例法】 class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:if not headA or not headB:return NonepointA headApointB headBwhile pointA ! pointB:pointA pointA.next if pointA else headB …

五、身份与访问管理—身份管理和访问控制管理(CISSP)

目录 1.身份管理 1.1 目录技术 1.2 单点登录 1.2.1 Kerberos认证 1.2.2 SESAME认证 1.2.3 KryptoKnight认证 1.3 联合身份管理 1.3.1 SAML安全断言标记语言 1.3.2 标记语言 1.3.3 OpenID 1.3.4 OAuth 1.3.5 OIDC(OpenID Connect) 2.身份即服务(IDaaS) 2.1 AA…

【CS.SE】端午节特辑:Docker容器化技术详解与实战

端午节, 先祝愿大家端午安康&#xff0c;阖家幸福, 哈哈&#xff01;这篇讲下Docker这一现代软件开发中不可或缺的技术。软件工程涉及软件开发的整个生命周期&#xff0c;包括需求分析、设计、构建、测试、部署和维护。Docker作为一种容器化技术&#xff0c;直接关联到软件部署…

基于springboot开发的Java MES制造执行系统源码,全套源码,一款数字化管理平台源码 云MES系统源码

基于springboot开发的Java MES制造执行系统源码&#xff0c;全套源码&#xff0c;一款数字化管理平台源码 云MES系统源码 MES系统源码相关技术&#xff1a; ​技术架构&#xff1a;springboot vue-element-plus-admin 开发语言&#xff1a;Java 开发工具&#xff1a;idea 前…

【西瓜书】6.支持向量机

目录&#xff1a; 1.分类问题SVM 1.1.线性可分 1.2.非线性可分——核函数 2.回归问题SVR 3.软间隔——松弛变量 3.1.分类问题&#xff1a;0/1损失函数、hinge损失、指数损失、对率损失 3.2.回归问题&#xff1a;不敏感损失函数、平方 4.正则化

机器学习——卷积神经网络

卷积神经网络CNN 多层感知机MLP的层数足够&#xff0c;理论上可以用其提取出二位特征&#xff0c;但是毕竟复杂&#xff0c;卷积神经网络就可以更合适的来提取高维的特征。 而卷积其实是一种运算 二维离散卷积的公式 可以看成g是一个图像的像素点&#xff0c;f是每个像素点对…

从反向传播过程看激活函数与权重初始化的选择对深度神经网络稳定性的影响

之前使用深度学习时一直对各种激活函数和权重初始化策略信手拈用&#xff0c;然而不能只知其表不知其里。若想深入理解为何选择某种激活函数和权重初始化方法卓有成效还是得回归本源&#xff0c;本文就从反向传播的计算过程来按图索骥。 为了更好地演示深度学习中的前向传播和…

Modbus主站和从站的区别

Modbus主站,从站 在工业自动化领域&#xff0c;Modbus是一种常用的通信协议&#xff0c;用于设备之间的数据交换。在Modbus通信中&#xff0c;主站和从站是两个关键的角色。了解主站和从站之间的区别对正确配置和管理Modbus网络至关重要。 Modbus主站的特点和功能 1.通信请求发…