【算法题】基础计算器的不同实现方式

本篇作者耗时一天半,整理理解,刚开始尝试自己的思路直接上手,手搓计算器,结果倒下了。。。。只能从头来,看了很多大佬的思路讲解,佩服佩服。


基础计算器

224. 基本计算器https://leetcode.cn/problems/basic-calculator/

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

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


方法一:(标准解法:运算符优先级比较+中缀转后缀+后缀运算)

1. 运算符优先级比较

cpp 复制代码
int operatorPrecedence(char ch)
    {
        struct opPD
        {
            char _op;
            int _pd;
        };
        static opPD arr[] = { {'+', 1},{'-', 1},{'*', 2},{'/', 2} };
        for (auto& e : arr)
        {
            if (e._op == ch)
            {
                return e._pd;
            }
        }
         assert(false);
        return -1;
    }

2. 中缀转后缀

cpp 复制代码
void toRPN(const string& s, size_t& i, vector<string>& v)
    {
        stack<char> st;
        while (i < s.size())
        {
            if (isdigit(s[i]))
            {
                // 运算数输出
                string num;
                while (i < s.size() && isdigit(s[i]))
                {
                    num += s[i];
                    ++i;
                }
                v.push_back(num);
            }
            else
            {
                if (s[i] == '(')
                {
                    // 递归⽅式处理括号中的⼦表达式
                    ++i;
                    toRPN(s, i, v);
                }
                else if (s[i] == ')')
                {
                    ++i;
                    //栈中的运算符全部输出
                    while (!st.empty())
                    {
                        v.push_back(string(1, st.top()));
                        st.pop();
                    }
                    // 结束递归
 
                    return;
                }
                else
                {
// 1、如果栈为空或者栈不为空且当前运算符⽐栈顶运算符优先级⾼,则当前运算符⼊栈
// 2、如果栈不为为空且⽐栈顶运算符优先级低或相等,说明栈顶的运算符可以运算了,
// 3、输出栈顶运算符,当前运算符继续⾛前⾯遇到运算符的逻辑
                  if (st.empty() || operatorPrecedence(s[i])>operatorPrecedence(st.top()))
                    {
                        st.push(s[i]);
                        ++i;
                    }
                    else
                    {
                        v.push_back(string(1, st.top()));
                        st.pop();
                    }
                }
            }
        }
        // 栈中的运算符全部输出
 
        while (!st.empty())
        {
            v.push_back(string(1, st.top()));
            st.pop();
        }
    }

3. 后缀运算

cpp 复制代码
int evalRPN(const vector<string>& tokens) {
        stack<long long> s;
        for (size_t i = 0; i < tokens.size(); ++i)
        {
            const string& str = tokens[i];
            // str为数字
            if (!("+" == str || "-" == str || "*" == str || "/" == str))
            {
                s.push(stoll(str));
            }
            else
            {
                // str为操作符
                long long right = s.top();
                s.pop();
                long long left = s.top();
                s.pop();
                    // 运算符
                     switch (str[0])
                {
                case '+':
                    s.push(left + right);
                    break;
                case '-':
                    s.push(left - right);
                    break;
                case '*':
                    s.push(left * right);
                    break;
                case '/':
                    s.push(left / right);
                    break;
                }
            }
        }
        return s.top();
    }

4. 详细代码

cpp 复制代码
class Solution {
public:
    int operatorPrecedence(char ch)
    {
        struct opPD
        {
            char _op;
            int _pd;
        };
        static opPD arr[] = { {'+', 1},{'-', 1},{'*', 2},{'/', 2} };
        for (auto& e : arr)
        {
            if (e._op == ch)
            {
                return e._pd;
            }
        }
         assert(false);
        return -1;
    }
    void toRPN(const string& s, size_t& i, vector<string>& v)
    {
        stack<char> st;
        while (i < s.size())
        {
            if (isdigit(s[i]))
            {
                // 运算数输出
                string num;
                while (i < s.size() && isdigit(s[i]))
                {
                    num += s[i];
                    ++i;
                }
                v.push_back(num);
            }
            else
            {
                if (s[i] == '(')
                {
                    // 递归⽅式处理括号中的⼦表达式
 
                    ++i;
                    toRPN(s, i, v);
                }
                else if (s[i] == ')')
                {
                    ++i;
                    //栈中的运算符全部输出
                    while (!st.empty())
                    {
                        v.push_back(string(1, st.top()));
                        st.pop();
                    }
                    // 结束递归
 
                    return;
                }
                else
                {// 1、如果栈为空或者栈不为空且当前运算符⽐栈顶运算符优先级⾼,则当前运算符⼊栈
                // 2、如果栈不为为空且⽐栈顶运算符优先级低或相等,说明栈顶的运算符可以运算了,
                // 输出栈顶运算符,当前运算符继续⾛前⾯遇到运算符的逻辑
                    if (st.empty() || operatorPrecedence(s[i]) > operatorPrecedence(st.top()))
                    {
                        st.push(s[i]);
                        ++i;
                    }
                    else
                    {
                        v.push_back(string(1, st.top()));
                        st.pop();
                    }
                }
            }
        }
        // 栈中的运算符全部输出
 
        while (!st.empty())
        {
            v.push_back(string(1, st.top()));
            st.pop();
        }
    }
    int evalRPN(const vector<string>& tokens) {
        stack<long long> s;
        for (size_t i = 0; i < tokens.size(); ++i)
        {
            const string& str = tokens[i];
            // str为数字
            if (!("+" == str || "-" == str || "*" == str || "/" == str))
            {
                s.push(stoll(str));
            }
            else
            {
                // str为操作符
                long long right = s.top();
                s.pop();
                long long left = s.top();
                s.pop();
                    // 运算符
                     switch (str[0])
                {
                case '+':
                    s.push(left + right);
                    break;
                case '-':
                    s.push(left - right);
                    break;
                case '*':
                    s.push(left * right);
                    break;
                case '/':
                    s.push(left / right);
                    break;
                }
            }
        }
        return s.top();
    }
    int calculate(string s) {
        // 1、去除所有空格,否则下⾯的⼀些逻辑没办法处理
        std::string news;
        news.reserve(s.size());
        for (auto ch : s)
        {
            if (ch != ' ')
                news += ch;
        }
        s.swap(news);
        news.clear();
        // 2、将所有的负数-x转换为0-x 
        for (size_t i = 0; i < s.size(); ++i)
        {
            if (s[i] == '-' && (i == 0 || (!isdigit(s[i - 1]) && s[i - 1] != ')')))
                news += "0-";
            else
                news += s[i];
        }
        size_t i = 0;
        vector<string> v;
        // 后缀表达式进⾏运算
         toRPN(news, i, v);
    
 
        return evalRPN(v);
        // 中缀表达式转成后缀表达式
    }
};

方法二:(处理括号+用栈判断括号内运算符)

详细代码:

cpp 复制代码
class Solution {
public:
    int calculate(string s) {
        int ans = 0, sign = 1, i = 0, n = s.size();
        stack<int> stk;
        stk.push(1);
        while (i < n) {
            if (s[i] == ' ') ++i;
            else if (s[i] == '+') {
                sign = stk.top();
                ++i;
            }
            else if (s[i] == '-') {
                sign = -stk.top();
                ++i;
            }
            else if (s[i] == '(') {
                stk.push(sign);
                ++i;
            }
            else if (s[i] == ')') {
                stk.pop();
                ++i;
            }
            else {
                long num = 0;
                while (i < n && '0' <= s[i] && s[i] <= '9') {
                    num = num * 10 + s[i] - '0';
                    ++i;
                }
                ans += sign * num;
            }
        }
        return ans;
    }
};

方法三:(用栈存储正负数据)

详细代码:

cpp 复制代码
int calculate(string s) {
    stack<int> stk;
    // 记录算式中的数字
    int num = 0;
    // 记录 num 前的符号,初始化为 +
    char sign = '+';
    for (int i = 0; i < s.size(); i++) {
        char c = s[i];
        // 如果是数字,连续读取到 num
        if (isdigit(c)) 
            num = 10 * num + (c - '0');
        // 如果不是数字,就是遇到了下一个符号,
        // 之前的数字和符号就要存进栈中
        if (!isdigit(c) || i == s.size() - 1) {
            switch (sign) {
                case '+':
                    stk.push(num); break;
                case '-':
                    stk.push(-num); break;
            }
            // 更新符号为当前符号,数字清零
            sign = c;
            num = 0;
        }
    }
    // 将栈中所有结果求和就是答案
    int res = 0;
    while (!stk.empty()) {
        res += stk.top();
        stk.pop();
    }
    return res;
}

扩展:(乘除法)

cpp 复制代码
for (int i = 0; i < s.size(); i++) {
    char c = s[i];
    if (isdigit(c)) 
        num = 10 * num + (c - '0');

    if (!isdigit(c) || i == s.size() - 1) {
        switch (sign) {
            int pre;
            case '+':
                stk.push(num); break;
            case '-':
                stk.push(-num); break;
            // 只要拿出前一个数字做对应运算即可
            case '*':
                pre = stk.top();
                stk.pop();
                stk.push(pre * num);
                break;
            case '/':
                pre = stk.top();
                stk.pop();
                stk.push(pre / num);
                break;
        }
        // 更新符号为当前符号,数字清零
        sign = c;
        num = 0;
    }
}

基础计算器ll

227. 基本计算器 IIhttps://leetcode.cn/problems/basic-calculator-ii/

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

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

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

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

cpp 复制代码
class Solution {
public:
    int calculate(string s) {
        stack <long long> a1;
        long long num = 0;
        char sign = '+'; // 初始化为 '+',以便正确处理字符串开头的第一个数字
        string st;
        
        // 1. 去除空格
        for(auto e : s) {
            if(!isspace(e)) {
                st.push_back(e);
            }
        }

        for(int i = 0; i < st.size(); i++) {
            if(isdigit(st[i])) {
                num = num * 10 + st[i] - '0';
            }
            
            // 当遇到操作符,或者到达字符串末尾时,处理前一个操作符 sign 和当前的数字 num
            if(!isdigit(st[i]) || i == st.size() - 1) {
                long long pre;
                switch(sign) {
                    case '+':
                        a1.push(num);
                        break; 
                    case '-':
                        a1.push(-num);
                        break; 
                    case '*':
                    {
                        pre = a1.top(); 
                        a1.pop();
                        a1.push(pre * num);
                        break; 
                    }                  
                    case '/':
                    {
                        pre = a1.top();
                        a1.pop();
                        a1.push(pre / num);
                        break; 
                    }  
                }
                sign = st[i]; // 更新当前操作符,留给下一次循环使用
                num = 0;      // 重置 num,准备计算下一个操作数
            }
        }
        
        // 3. 累加栈中所有结果
        long long add = 0;
        while(!a1.empty()) {
            add = add + a1.top();
            a1.pop();
        }
        return (int)add;
    }
};

相关推荐
Sunsets_Red1 小时前
P12375 「LAOI-12」MST? 题解
c++·算法·洛谷·信息学·oier·洛谷题解
雪度娃娃1 小时前
多用户任务管理器
c++·个人开发
_深海凉_1 小时前
LeetCode热题100-二叉树的直径
算法·leetcode·职场和发展
shylyly_1 小时前
大小端字节序
数据结构·算法·联合体·大小端字节序·字节序判断
mmz12071 小时前
深度优先搜索DFS3(c++)
c++·算法·深度优先
水蓝烟雨1 小时前
3373. 连接两棵树后最大目标节点数目 II
算法·leetcode
故事和你911 小时前
洛谷-【图论2-1】树6
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
sali-tec1 小时前
C# 基于OpenCv的视觉工作流-章73-点-线距离
图像处理·人工智能·opencv·算法·计算机视觉
不知名的老吴1 小时前
在C++中不用宏怎么打日志的使用建议
开发语言·c++·算法