基本计算器II

题目描述

来源于 https://leetcode-cn.com/

实现一个基本的计算器来计算一个简单的字符串表达式的值。

字符串表达式仅包含非负整数,+-*/ 四种运算符和空格  。 整数除法仅保留整数部分。

示例 1:

输入: "3+2*2"
输出: 7

示例 2:

输入: " 3/2 "
输出: 1

示例 3:

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

说明:

  • 你可以假设所给定的表达式都是有效的。
  • 不要使用内置的库函数 eval

解法一:基于后缀表达式

如下这种正常的这种表达式,很不利于计算机直接求值,人在计算它的结果时,首先会看一下优先级,先计算括号里面的,先计算乘除法,然后加减法。我们人其实是对整个表达式经过了多次扫描,然后不断计算其中的某个部分,最终把整个表达式计算完成。但这种方法,对计算机而言不够高效,我们期望它能一遍扫描就计算完成。

1 + 4 * 5 + 2 * (3 + 1)

我们把整个表达式转化为后缀表达式:

1 4 5 * + 2 3 1 + * +

后文会提到如何得到后缀表达式,这里先看看后缀表达式的求值。遍历每个 token,遇到符号之后,就对该符号前面的两个数进行运算得到一个结果,这就可以消除两个数字和一个符号。如此持续运算,最终会得到运算结果。上面的表达式计算过程如下:

1 4 5 * + 2 3 1 + * +
1 20 + 2 3 1 + * +      # 对 4 5 * 求值得到 20
21 2 3 1 + * +          # 对 1 20 + 求值得到 21
21 2 4 * +              # 对 3 1 + 求值得到 4
21 8 +                  # 对 2 4 * 求值得到 8
29                      # 对 21 8 + 求值得到 29

计算过程就是使用一个栈,遇到数字就入栈,遇到符号就出栈两个数,然后将计算结果再次入栈。这种基于后缀表达式的方法,计算步骤非常简单,但前提是要把原表达式转化为后缀表达式。

整个表达式可以看作是一个二叉树,其中叶子节点是数字,内部节点是运算符号。常规的表达式是二此二叉树的中序遍历,而后缀表达式就是此二叉树的后序遍历。但是我们不能通过中序遍历的序列得到后序遍历序列。其实上面的表达式可以表示成多种不同的树,上面的这个树只是其中一种而已。

好在我们能够得到一个不改变原表达式值的后缀表达式,它的过程是这样的:

使用 seq 表示后缀表达式序列,使用一个栈保存操作符。

  1. 遍历原序列,记每个 token 为 t
  2. 如果 t 是数字,就把它加入 seq
  3. 如果 t 是操作符
    • 如果栈非空,就把栈中优先级比 t 更高的运算符 pop 出来并加入 seq 中,然后把 t 入栈。
    • 如果栈为空,就把 t 入栈
  4. 如果 t 是左括号,就入栈。如果 t 是右括号,就不断出栈直到遇到左括号(抛弃左括号)
  5. 遍历完原序列后,把栈中余下的内容加入到 seq 中

下面是完整的实现代码,支持加减乘除和括号。

// 输入的是字符串,其中包含数字和操作符号,这里把每个数字和操作符号转为一个 Token
// kind 表示符号类型,可取值为 '+', '-', '*', '/', '(', ')' 和 0,其中 0 表示这个 Token 是数字
// 如果支持浮点数,只需要修改 Token 中 value 的数据类型,并且修改 tokenize 方法即可
struct Token{
    char kind;
    int value;
};

class Solution {
public:
    int calculate(string s) {
        vector<Token> tokens = tokenize(s);

        vector<Token> postOrderTokens = inOrderToPostOrder(tokens);

        int res = evalPostOrder(postOrderTokens);

        return res;
    }

private:
    // 对后缀表达式序列求值
    static int evalPostOrder(const vector<Token>& postOrderTokens){
        stack<int> values;
        for(const Token& t: postOrderTokens){
            if(t.kind == 0){
                values.push(t.value);
            }else{
                int b = values.top();
                values.pop();
                int a = values.top();
                values.pop();
                switch (t.kind) {
                    case '+':
                        values.push(a + b);
                        break;
                    case '-':
                        values.push(a - b);
                        break;
                    case '*':
                        values.push(a * b);
                        break;
                    case '/':
                        values.push(a / b);
                    default:
                        break;
                }
            }
        }
        return values.top();
    }

    // 转化中序序列为后序序列
    static vector<Token> inOrderToPostOrder(const vector<Token>& tokens){
        vector<Token> res;
        stack<Token> stk;

        for(const Token& t: tokens){
            switch (t.kind) {
                case 0: // kind == 0 表示数字
                    res.push_back(t);
                    break;
                case '(':
                    stk.push(t);
                    break;
                case ')':
                    while (stk.top().kind !=  '('){
                        res.push_back(stk.top());
                        stk.pop();
                    }
                    stk.pop(); // pop '('
                    break;
                case '+':
                case '-':
                case '*':
                case '/':
                    while(!stk.empty() && stk.top().kind != '(' &&
                        compare_priority(stk.top().kind, t.kind) >= 0){

                        res.push_back(stk.top());
                        stk.pop();
                    }
                    stk.push(t);
                default:
                    break;
            }
        }
        while(!stk.empty()){
            res.push_back(stk.top());
            stk.pop();
        }
        return res;
    }

    // 比较两个运算符的优先级:
    // a < b   -> -1
    // a == b  -> 0
    // a > b   -> 1
    static int compare_priority(char a, char b){
        if (a == '+' || a == '-'){
            if(b == '+' || b == '-'){
                return 0;
            }else{
                return -1;
            }
        }
        if(a == '*' || a == '/'){
            if(b == '*' || b == '/'){
                return 0;
            }else{
                return 1;
            }
        }
        return 0;
    }

    // 把输入字符串转化为 Token 序列
    static vector<Token> tokenize(const string& s){
        vector<Token> tokens;
        int i = 0;
        while(i < s.size()){
            if(s[i] == ' '){
                i++;
                continue;
            }
            if(!isdigit(s[i])){
                tokens.push_back(Token{.kind = s[i], .value = 0});
                i++;
                continue;
            }
            int n = 0;
            while(isdigit(s[i])){
                n = n * 10 + (s[i] - '0');
                i++;
            }
            tokens.push_back(Token{.kind = 0, .value = n});
        }
        return tokens;
    }
};

解法二:递归下降

观察上面这个表达式树,如果先左右孩子均为数值的子树求值,消除一个子树,继续以上过程,整个表达式的值就能求出来了。

使用递归下降来对表达式求值,其本质还是对表达式树进行求值,基于递归的思想,先计算最最基本的子树(左右孩子均为数值),然后一步步第求出整个表达式的结果。这个解法比较复杂,需要大致了解一下编译原理。这里我不打算写了。

首先写出四则运算表达式的文法:

expression := term
            | expression + term
            | expression - term

term := primary
      | term * primary
      | term / primary

primary := number
        | ( expression )
        | - primary
        | + primary

基于文法使用递归下降对表达式进行求值:

struct Token{
    char kind;
    int value;
};

class Solution{
public:
    int calculate(const string& exp){
        this->tokens_ = tokenize(exp);
        return expression();
    }

private:
    int expression(){
        int lhs = term();
        Token t = lookahead(0);
        while(true){
            switch (t.kind) {
                case '+':
                    consume(t.kind);
                    lhs += term();
                    t = lookahead(0);
                    break;
                case '-':
                    consume(t.kind);
                    lhs -= term();
                    t = lookahead(0);
                    break;
                default:
                    return lhs;
            }
        }
    }

    int term(){
        int lhs = primary();
        Token t = lookahead(0);
        while (true){
            switch (t.kind) {
                case '*':
                    consume(t.kind);
                    lhs *= primary();
                    t = lookahead(0);
                    break;
                case '/':
                    consume(t.kind);
                    lhs /= primary();
                    t = lookahead(0);
                    break;
                default:
                    return lhs;
            }
        }
    }

    int primary(){
        Token t = lookahead(0);
        consume(t.kind);
        switch (t.kind) {
            case '(': {
                int d = expression();
                consume(')');
                return d;
            }
            case '0':
                return t.value;
            case '-':
                return -primary();
            case '+':
                return primary();
            default:
                throw logic_error("invalid expression");
        }
    }

    Token lookahead(int i){
        if(index_ + i < tokens_.size()){
            return tokens_[index_ + i];
        }else{
            return Token{.kind = -1, .value = 0};
        }
    }

    void consume(int kind){
        if(index_ < tokens_.size()){
            if(tokens_[index_].kind == kind){
                index_++;
            }else{
                throw logic_error("invalid expression");
            }
        }else{
            logic_error("invalid expression");
        }
    }
    
    static vector<Token> tokenize(const string& s){
        vector<Token> tokens;
        int i = 0;
        while(i < s.size()){
            if(s[i] == ' '){
                i++;
                continue;
            }
            if(!isdigit(s[i])){
                tokens.push_back(Token{.kind = s[i], .value = 0});
                i++;
                continue;
            }
            int n = 0;
            while(isdigit(s[i])){
                n = n * 10 + (s[i] - '0');
                i++;
            }
            tokens.push_back(Token{.kind = 0, .value = n});
        }
        return tokens;
    }
private:
    vector<Token> tokens_;
    int index_ = 0;
};