【LeetCode-772】基本计算器 III

问题

实现一个基本的计算器来计算简单的表达式字符串(带括号的四则运算)。

表达式字符串只包含非负整数,算符 +、-、*、/ ,左括号 ( 和右括号 ) 。整数除法需要 向下截断 。

你可以假定给定的表达式总是有效的。

示例

输入:s = "2*( - 5+5*2)/ 3+(6/2+8)"
输出:14

解答

class Solution {
public:
    int calculate(string s) {
        trim(s);
        mid2post(s);
        return post2res();
    }
private:
    vector<string> postfix;
    stack<char> opStk;
    unordered_map<char, int> getPr{{'(', 0}, {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
    void trim(string& s){ // 将'(-'转换为'(0-',避免特殊情况讨论
        int i = 0;
        for (char j : s)
            if (j != ' ') s[i++] = j;
        s.resize(i);
        int pos = s.find("(-");
        while(pos != -1){
            s.replace(pos, 2, "(0-");
            pos = s.find("(-");
        }
    }
    void mid2post(string s) {
        int n = s.size();
        for (int i = 0; i < n; i++) {
            if (isdigit(s[i])) { //当前是数字
                string curNum;
                while (i < n && isdigit(s[i]))
                    curNum += s[i++];
                i--;
                postfix.push_back(curNum);
            }
            else if (s[i]=='(') opStk.push('('); //当前是左括号
            else if (s[i]==')'){ //当前是右括号
                while(!opStk.empty() && opStk.top()!='('){
                    postfix.push_back(string(1, opStk.top()));
                    opStk.pop();
                }
                opStk.pop(); //左括号也要弹出来
            }
            else { //当前是 + - * / 这些符号
                // 维护一个严格单调递增栈opStk,对于不比当前符号优先级高的,可以加入后缀表达式直接计算
                while (!opStk.empty() && getPr[opStk.top()] >= getPr[s[i]]) {
                    postfix.push_back(string(1, opStk.top()));
                    opStk.pop();
                }
                opStk.push(s[i]); 
            }
        }
        while (!opStk.empty()) {
            postfix.push_back(string(1, opStk.top()));
            opStk.pop();
        }
    }
    int post2res() {
        stack<int> numStk;
        numStk.push(0);
        for (string& cur : postfix) {
            if (isdigit(cur[0])) numStk.push(stoi(cur));
            else {
                int num2 = numStk.top(); numStk.pop();
                int num1 = numStk.top(); numStk.pop();
                switch (cur[0]) {
                    case '+': numStk.push(num1 + num2); break;
                    case '-': numStk.push(num1 - num2); break;
                    case '*': numStk.push(num1 * num2); break;
                    case '/': numStk.push(num1 / num2);
                }
            }
        }
        return numStk.top();
    }
};

重点思路

需要实现中缀转后缀,然后后缀求解最终结果两个步骤。本方法的重难点是第一步:中缀转后缀。中缀表达式就是我们平时看到的普通的式子,符号在数字中间;后缀表达式计算机更容易编程实现(符号优先级体现在字符顺序中),符号在数字后面。

中缀转后缀

这一步使用两个数据结构:

  1. opStk用于存储符号的栈,该栈括号内符号优先级严格递增,即在括号内为一个符号优先级单调栈。这里解释一下为什么需要维护单调递增栈。后缀表达式中,越靠前的符号,其符号优先级越高(因为会先算),而单调递增栈的特点是能找到下一个比当前值小的数,在这之前的栈顶元素都大于等于当前值。那么对于这个符号来说,出栈的符号优先级都不比这个符号低,需要先计算,可以立即加入后缀表达式中;
  2. postfix用于存储转换后的后缀表达式。

整个转换流程如下。

  • 输入的是数字
    • 直接加入postfix
  • 输入的是左括号
    • 左括号直接加入opStk,用于限制输入右括号时的符号弹出数量。
  • 输入的是右括号
    • 弹出opStk中所有符号,直至遇到左括号。
  • 输入的是运算符
    • 确定优先级顺序:( < + - < * /
    • opStk为空或者s[i]优先级大于opStk栈顶元素时,s[i]入栈opStk
    • s[i]优先级小于等于opStk栈顶元素时,将大于或等于s[i]优先级的符号依次出栈至后缀表达式postfix中。

后缀转结果

这一步相比起来就简单很多了,使用一个栈numStk存储数字,当遇到符号时,使用该符号运算栈顶两个元素,先出栈的在符号右边。

posted @ 2021-03-11 11:49  tmpUser  阅读(587)  评论(0编辑  收藏  举报