【每日一题】中缀表达式计算详解(基本计算器 II,表达式计算4)

在这里插入图片描述

题目描述


227. 基本计算器 II
151. 表达式计算4

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。

示例 1:

输入:s = “3+2*2”
输出:7
示例 2:

输入:s = " 3/2 "
输出:1
示例 3:

输入:s = " 3+5 / 2 "
输出:5

提示:

1 <= s.length <= 3 * 105
s 由整数和算符 (‘+’, ‘-’, ‘*’, ‘/’) 组成,中间由一些空格隔开
s 表示一个 有效表达式
表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1] 内
题目数据保证答案是一个 32-bit 整数

题解


表达式与遍历强相关。与二叉树有关系,所以与栈有关系,因为栈与二叉树有着很深的关系。

错误示范

坑: 这个字符’+‘与to_stirng(’+‘) ,实际上就是把这个’+'字符放到string当中,也就是"43",因为题目当中的"43",这种就会被误认为43是‘+’号,所以这种做法是不行的。
当然这是因为我把字符先将整数和操作符都切分成字符串放入numstr当中。

class Solution {
public:
    int calculate(string a) {
        stringstream ss(a);
        string s;
        string tmp;
        while (ss >> tmp)
        {
            s += tmp;
        }
        stack<int> numst;
        stack<string> symbolst;
        vector<string> numstr;
        int pre = 0, cur = 0;
        unordered_map<string, int> um;
        um[to_string('+')] = 1;
        um[to_string('-')] = 1;
        um[to_string('*')] = 2;
        um[to_string('/')] = 2;
        while (cur < s.size())
        {
            while (pre < s.size() && um.find(to_string(s[pre])) != um.end())
            {
                pre++;
            }
            while (cur < s.size() && um.find(to_string(s[cur])) == um.end())
            {
                cur++;
            }
            if (um.find(to_string(s[pre])) == um.end())
            {
                numstr.push_back(s.substr(pre, cur - pre));
                if (cur != s.size())
                {
                    numstr.push_back(to_string(s[cur]));
                }
                pre = cur;
                cur++;
            }
        }

        unordered_map<string, function<int(int, int)>> funtionum = {
            make_pair(to_string('+'),[](int x,int y) {return x + y; }),make_pair(to_string('-'),[](int x,int y) {return x - y; }),
            make_pair(to_string('*'),[](int x,int y) {return x * y; }),make_pair(to_string('/'),[](int x,int y) {return x / y; })
        };
        for (int i = 0; i < numstr.size(); ++i)
        {
            if (um.count(numstr[i]) != 0)
            {
                if (!symbolst.empty() && um[to_string(s[i])] <= um[symbolst.top()])
                {
                    int y = numst.top();
                    numst.pop();
                    int x = numst.top();
                    numst.pop();
                    string ch = symbolst.top();
                    symbolst.pop();
                    int res = funtionum[ch](x, y);
                    numst.push(res);
                }
                symbolst.push(to_string(s[i]));
            }
            else
            {
                numst.push(atoi(numstr[i].c_str()));
            }
        }
        while (!symbolst.empty())
        {
            int y = numst.top();
            numst.pop();
            int x = numst.top();
            numst.pop();
            string ch = symbolst.top();
            symbolst.pop();
            int res = funtionum[ch](x, y);
            numst.push(res);
        }

        return numst.top();
    }
};

正确答案


class Solution {
public:
    int calculate(string a) {
        stringstream ss(a);
        string s;
        string tmp;
        while (ss >> tmp)
        {
            s += tmp;
        }
        stack<int> numst;
        stack<char> symbolst;
        unordered_map<char, int> compareum;
        compareum['+'] = 1;
        compareum['-'] = 1;
        compareum['*'] = 2;
        compareum['/'] = 2;

        unordered_map<char, function<int(int, int)>> funtionum = {
            make_pair('+',[](int x,int y) {return x + y; }),make_pair('-',[](int x,int y) {return x - y; }),
            make_pair('*',[](int x,int y) {return x * y; }),make_pair('/',[](int x,int y) {return x / y; })
        };
        int cur = 0;
        while (cur < s.size())
        {
            if (s[cur] >= '0' && s[cur] <= '9')
            {
                int tmp = 0;
                while (cur < s.size() && s[cur] >= '0' && s[cur] <= '9')
                {
                    tmp *= 10;
                    tmp += s[cur] - '0';
                    cur++;
                }
                numst.push(tmp);
            }
            else
            {
                while (!symbolst.empty() && compareum[s[cur]] <= compareum[symbolst.top()])
                {
                    int y = numst.top();
                    numst.pop();
                    int x = numst.top();
                    numst.pop();
                    char ch = symbolst.top();
                    symbolst.pop();
                    int res = funtionum[ch](x, y);
                    numst.push(res);
                }
                symbolst.push(s[cur]);
                ++cur;
            }
        }

        while (!symbolst.empty())
        {
            int y = numst.top();
            numst.pop();
            int x = numst.top();
            numst.pop();
            char ch = symbolst.top();
            symbolst.pop();
            int res = funtionum[ch](x, y);
            numst.push(res);
        }

        return numst.top();
    }
};

比较完美的答案(重要)


解析计算器的一个步骤

  • 需要两个栈,一个存放数值,一个存放运算符,如 1 * 1 - 2 ^ 2 ,当我们遍历到 -的时候才知道前面的 1*1可以计算,同理,遍历到-号的时候不能确定是否能够执行,只有当前运算符出现比栈顶的运算符优先级低的时候,说明栈顶是优先级高的运算符,此时才可以拿出来计算。
  • 如果同等级的运算符,如 + 号 后遇到 + 号,那么前面的+号要比后面的+号的优先级高,因为我们默认同等级运算符是从左往后运算的。也就是前一个+可以出栈了。
  • 需要考虑遇到 括号()的问题,实际上是否遇到括号可以用同一种逻辑表示,只需要入左括号(,遇到右括号的时候将栈当中元素拿出来计算,知道运算符栈的栈顶元素是(,从始至终)是不需要入栈的,所以为了保证测试用例当中出现(1+2))))))))这种测试用例,我们统一可以添加字符串长度的左括号在字符串前面,这样就保证了每一个右括号一定有一个左括号匹配。
  • 并且为了扩展性更强,定义了两个成员变量 pri_um ,operator_um ,pri_um 可以用来自定义运算符和对应优先级的映射,operator_um 可以用来规定自定义运算符和运算方法。

至此,下面的代码实际上更加全面,甚至能够自定义运算符的运算方式和优先等级。

1+(((3+2) 等用例的出现导致我们必须在结尾进行判断,当然也可以添加相同数量的右扩号解决。

#include<iostream>
using namespace std;
#include<string>
#include<stack>
#include<unordered_map>
#include<functional>
#include<sstream>
#include<cmath>
class Solution {
public:
    stack<int> _numst;
    stack<char> _operatorst;
    unordered_map<char, int> pri_um = {
        {'+',1},
        {'-',1},
        {'*',2},
        {'/',2},
        {'^',3},
    };
    unordered_map<char, function<int(int, int)>> operator_um = {
        {'+', [](int x,int y) {return  x + y; }},
        {'-', [](int x,int y) {return x - y; }},
        {'*', [](int x,int y) {return x * y; }},
        {'/', [](int x,int y) {return x / y; }},
        {'^', [](int x,int y) {return pow(x,y); }},
    };

    int calculate(string s) {
        string l = "(";
        for (size_t i = 0; i < s.size(); ++i)
        {
            l += '(';
        }
        l += s;
        s = move(l);
        //s += ')';
        _numst.push(0);// -1 + 2 ,若先入0,后续会入 负号, 再入1 就和正常计算一样了,若第一个数是正数,后续st计算完这个也不会使用到。
        for (int i = 0; i < s.size(); ++i)
        {
            if (s[i] == ' ') continue;//第一种情况,遇到空格
            else if (s[i] == '(')//遇到左括号
            {
                //入左括号,(-1 + 2) ,判断是否是以负数开头
                _operatorst.push(s[i]);
                if (i + 1 < s.size() && s[i + 1] == '-')
                {
                    _numst.push(0);
                    _operatorst.push('-');
                    ++i;
                }
            }
            else if (s[i] == ')') // 此时_operatorst当中直接一个个出,因为当中的优先级是已经排序好的,以(结尾即可
            {
                //右括号,此时不需要入右括号,只需要计算到栈顶是(为止,并把左括号弹出
                while (_operatorst.top() != '(')//此时严格'('的数量多余')',当遍历到'('说明区间内的数据都计算完全了~
                {
                    cal();
                }
                _operatorst.pop();//去掉 '(',此时严格'('的数量多余')'
            }
            else if (s[i] >= '0' && s[i] <= '9')//数字中穿插空格的情况不考虑,这个应用层的问题
            {//若是正常正常数字,则进行计算
                int pre = i;
                //while (i + 1 < s.size() && pri_um.find(s[i + 1]) == pri_um.end())//即找到不是操作符,error
                while (i + 1 < s.size() && s[i + 1] >= '0' && s[i + 1] <= '9')//即往后找的一定还是数字
                {
                    i++;
                }
                int res = stoi(s.substr(pre, i - pre + 1));
                _numst.push(res);
            }
            else
            {//即操作符st为空,或者操作符st栈顶是(,或者是操作符的优先级,opers.top()!='('括号不匹配才会有这个问题。
                while (!_operatorst.empty() && _operatorst.top() != '(' && pri_um[s[i]] <= pri_um[_operatorst.top()])
                {
                    cal();
                }
                _operatorst.push(s[i]);
            }
        }
        while (!_operatorst.empty())
        {
            if (_operatorst.top() != '(')
            {
                cal();
            }
            else
            {
                _operatorst.pop();
            }
        }
        return _numst.top();
    }
	//只做一次计算,从_numst拿两个元素,_operatorst拿一个运算符做计算
    int cal()
    {
        int r = _numst.top(); _numst.pop();
        int l = _numst.top(); _numst.pop();
        char op = _operatorst.top(); _operatorst.pop();
        int res = operator_um[op](l, r);
        _numst.push(res);

        return res;
    }
};




int main()
{
    string str;
    getline(cin,str);
    Solution s1;
    cout << s1.calculate(str) << endl;
    return 0;
}

当然,若是不想加入最后一层while循环,实际上可以在s字符串添加相同数量的),只需要在遍历到)的时候判断需要改动,如是否有操作符以及是否是(,以及对出(做判断,因为此时不一定满足(的数量大于)

#include<iostream>
using namespace std;
#include<string>
#include<stack>
#include<unordered_map>
#include<functional>
#include<sstream>
#include<cmath>
class Solution {
public:
    stack<int> _numst;
    stack<char> _operatorst;
    unordered_map<char, int> pri_um = {
        {'+',1},
        {'-',1},
        {'*',2},
        {'/',2},
        {'^',3},
    };
    unordered_map<char, function<int(int, int)>> operator_um = {
        {'+', [](int x,int y) {return  x + y; }},
        {'-', [](int x,int y) {return x - y; }},
        {'*', [](int x,int y) {return x * y; }},
        {'/', [](int x,int y) {return x / y; }},
        {'^', [](int x,int y) {return pow(x,y); }},
    };

    int calculate(string s) {
        string l = "(";
        int sz = s.size();
        for (size_t i = 0; i < s.size(); ++i)
        {
            l += '(';
        }
        l += s;
        s = move(l);
        for (size_t i = 0; i < sz; ++i)
        {
            s += ')';
        }
        //cout << s << endl;
        //s += ')';
        _numst.push(0);// -1 + 2 ,若先入0,后续会入 负号, 再入1 就和正常计算一样了,若第一个数是正数,后续st计算完这个也不会使用到。
        for (int i = 0; i < s.size(); ++i)
        {
            if (s[i] == ' ') continue;//第一种情况,遇到空格
            else if (s[i] == '(')//遇到左括号
            {
                //入左括号,(-1 + 2) ,判断是否是以负数开头
                _operatorst.push(s[i]);
                if (i + 1 < s.size() && s[i + 1] == '-')
                {
                    _numst.push(0);
                    _operatorst.push('-');
                    ++i;
                }
            }
            else if (s[i] == ')') // 此时_operatorst当中直接一个个出,因为当中的优先级是已经排序好的,以(结尾即可
            {
                //右括号,此时不需要入右括号,只需要计算到栈顶是(为止,并把左括号弹出
                while (!_operatorst.empty() && _operatorst.top() != '(')
                {
                    cal();
                }
                if (!_operatorst.empty())
                    _operatorst.pop();//去掉 '('
            }
            else if (s[i] >= '0' && s[i] <= '9')//数字中穿插空格的情况不考虑,这个应用层的问题
            {//若是正常正常数字,则进行计算
                int pre = i;
                //while (i + 1 < s.size() && pri_um.find(s[i + 1]) == pri_um.end())//即找到不是操作符
                while (i + 1 < s.size() && s[i + 1] >= '0' && s[i + 1] <= '9')//即找到不是操作符
                {
                    i++;
                }
                int res = stoi(s.substr(pre, i - pre + 1));
                _numst.push(res);
            }
            else
            {//即操作符st为空,或者操作符st栈顶是(,或者是操作符的优先级,opers.top()!='('括号不匹配才会有这个问题。
                while (!_operatorst.empty() && _operatorst.top() != '(' && pri_um[s[i]] <= pri_um[_operatorst.top()])
                {
                    cal();
                }
                _operatorst.push(s[i]);
            }
        }
        return _numst.top();
    }

    int cal()
    {
        int r = _numst.top(); _numst.pop();
        int l = _numst.top(); _numst.pop();
        char op = _operatorst.top(); _operatorst.pop();
        int res = operator_um[op](l, r);
        _numst.push(res);

        return res;
    }
};




int main()
{
    string str;
    getline(cin,str);
    Solution s1;
    cout << s1.calculate(str) << endl;
    return 0;
}

参考:
https://www.acwing.com/solution/content/39453/



end

  • 喜欢就收藏
  • 认同就点赞
  • 支持就关注
  • 疑问就评论

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340