770. Basic Calculator IV

Given an expression such as expression = "e + 8 - a + 5" and an evaluation map such as {"e": 1} (given in terms of evalvars = ["e"] and evalints = [1]), return a list of tokens representing the simplified expression, such as ["-1*a","14"]

  • An expression alternates chunks and symbols, with a space separating each chunk and symbol.
  • A chunk is either an expression in parentheses, a variable, or a non-negative integer.
  • A variable is a string of lowercase letters (not including digits.) Note that variables can be multiple letters, and note that variables never have a leading coefficient or unary operator like "2x" or "-x".

Expressions are evaluated in the usual order: brackets first, then multiplication, then addition and subtraction.

  • For example, expression = "1 + 2 * 3" has an answer of ["7"].

The format of the output is as follows:

  • For each term of free variables with a non-zero coefficient, we write the free variables within a term in sorted order lexicographically.
    • For example, we would never write a term like "b*a*c", only "a*b*c".
  • Terms have degrees equal to the number of free variables being multiplied, counting multiplicity. We write the largest degree terms of our answer first, breaking ties by lexicographic order ignoring the leading coefficient of the term.
    • For example, "a*a*b*c" has degree 4.
  • The leading coefficient of the term is placed directly to the left with an asterisk separating it from the variables (if they exist.) A leading coefficient of 1 is still printed.
  • An example of a well-formatted answer is ["-2*a*a*a", "3*a*a*b", "3*b*b", "4*a", "5*c", "-6"].
  • Terms (including constant terms) with coefficient 0 are not included.
    • For example, an expression of "0" has an output of [].

Example 1:

Input: expression = "e + 8 - a + 5", evalvars = ["e"], evalints = [1]
Output: ["-1*a","14"]

Example 2:

Input: expression = "e - 8 + temperature - pressure", evalvars = ["e", "temperature"], evalints = [1, 12]
Output: ["-1*pressure","5"]

Example 3:

Input: expression = "(e + 8) * (e - 8)", evalvars = [], evalints = []
Output: ["1*e*e","-64"]

Example 4:

Input: expression = "a * b * c + b * a * c * 4", evalvars = [], evalints = []
Output: ["5*a*b*c"]

Example 5:

Input: expression = "((a - b) * (b - c) + (c - a)) * ((a - b) + (b - c) * (c - a))", evalvars = [], evalints = []
Output: ["-1*a*a*b*b","2*a*a*b*c","-1*a*a*c*c","1*a*b*b*b","-1*a*b*b*c","-1*a*b*c*c","1*a*c*c*c","-1*b*b*b*c","2*b*b*c*c","-1*b*c*c*c","2*a*a*b","-2*a*a*c","-2*a*b*b","2*a*c*c","1*b*b*b","-1*b*b*c","1*b*c*c","-1*c*c*c","-1*a*a","1*a*b","1*a*c","-1*b*c"]

Constraints:

  • 1 <= expression.length <= 250
  • expression consists of lowercase English letters, digits, '+''-''*''('')'' '.
  • expression does not contain any leading or trailing spaces.
  • All the tokens in expression are separated by a single space.
  • 0 <= evalvars.length <= 100
  • 1 <= evalvars[i].length <= 20
  • evalvars[i] consists of lowercase English letters.
  • evalints.length == evalvars.length
  • -100 <= evalints[i] <= 100

这道题是基本计算器系列的第四道,说真的,这哪是基本计算器啊,简直是 Super Advanced Calculator 啊,感觉超级复杂啊,上千的 dislike,和论坛上的疯狂吐槽,无一不应证了这道题的可怕。博主翻遍了论坛上的解法,大多数的 C++ 或 Java 的解法都巨长无比,找来找去也就 Hanafubuki 大神的帖子 相对来说能短一些,这里就照着这个来讲解吧。这道题的难点在于如何让编译器按照我们人类的思维来化简表达式,需要将已知的变量的值带入到表达值里,并把相同项归类合并,比如常数项都放一起计算出一个最终值,相同的变量都合并计算成一个最终的系数,最终剩下的都是无法再继续合并的项,也就是最终结果了。所以每个变量都是独一无二的,系数有可能相同,比如例子5中的那些 aabb, aabc 等等就是变量,而前面的数字就是系数了。为了方便相同项进行合并,可以建立变量和其系数之间的映射,那么如何表示变量呢,是直接用个字符串吗,比如 a*a*b*c 么,这样是不行的,因为还存在个顺序问题,比如 a*b*ca 相乘,必须变成 a*a*b*c,而不是 a*b*c*a

这样的话,最好将这些相乘的变量分开单独存,等乘完了之后,排个序再生成最终结果。这里可以用一个字符串数组来表示整体的变量集,每个单独的变量分别存在一个字符串里,这个字符串数组就是映射的 key,映射到系数上。由于题目中还对最终的排序有要求,幂数大的项放前面,即相乘的变量个数多的项放前面,也就是字符串数组长度大的放前面。既然对顺序有要求,就得用 TreeMap 了,但是默认的排序方式是不满足题目要求的,这里需要用自定义的排序方式,即字符串数组长度大的放前面,若长度相等,则按照字母顺序来排,C++ 中的数组之间是可以用大于小于符号来直接做比较的。题目中将已知变量和其值分别放到了两个不同的数组之中,这样不太方便查找,可以建立已知变量和其值之间的映射,用 HashMap 表示。然后就是要进行表达式化简了,这是最难的地方,放到一个子函数中处理,返回值是自定义排序方式的 TreeMap,即用字符串数组表示的变量和其系数之间的映射。

在子函数中,先声明两个这样的 TreeMap,分别是 local 和 res,其中 local 保存的是计算过程中的结果,res 是最终需要返回的结果。将空串和1的映射对儿先放到 local 中,这里的空串表示没有变量,即常数项,映射值就是常数项的值,初始化为1。接下来就是遍历这个表达式了,因为给定的表达式是一个字符串,必须将每个部分提取出来。当遇到空格的时候,直接跳过,当遇到字母或者数字的时候,加入到变量 sub 之中。若遇到了左括号时,此时需要将整个括号中的内容提取出来,放到 sub 中。于是这里应用到了经典的判断合法括号的方法,用一个变量 cnt 来记录左括号的个数,遇到左括号则自增1,遇到右括号则自减1,当 cnt 为0时,则表示最外层括号闭合了,此时就可以 break 循环了,则最外层括号中的内容都可以添加到 sub 中了。否则若遇到运算符(或i等于n,这里i还要遍历到n是因为最后遍历完表达式之后还需要触发一次处理),此时判断,若 sub 的长度正好等于n了,说明整个表达式就是一个变量或者一个数字,若 sub 在 evalMap 中存在,则说明是已知变量,则从 evalMap 中取出变量值,建立和1的映射并返回。若是个数字的话,则转为整型数,建立和1的映射并返回。否则就将这个变量建立和1的映射并返回。

之后就要进行相乘的运算了,建立自定义排序的 TreeMap 的变量 mult 和 t,其中对 sub 调用子函数的结果存到t中。为啥还要对 sub 调用子函数呢,因为 sub 有可能还是一个需要进一步解析的表达式,比如前面提到的遇到括号的时候,就把括号中所有的内容提取到了 sub,这样 sub 里面有可能还是一个子表达式,不能直接进行归纳合并。现在需要将 local 中的映射对儿和 t 中的映射对儿分别两两相乘,就像乘法的分配律一样,将乘完的结果保存到 mult 中。分别从 local 和 t 中取出一个映射对儿,将二者的字符串数组合并为一个,并进行排序,形成的新的 key 放到 mult 中(有可能已经存在了),映射值加上之前二者映射值的乘积。算完了之后将 mult 中的内容移到 local 中,然后判断若当前符号是加或减(或者i等于n了),则需要将 local 中的内容移到结果 res 中,注意系数要乘以当前的符号 sign,然后符号 sign 更新为当前的符号,然后将 local 重置即可。当最终的子函数退出了之后,就需要将结果转为题目中要求的形式了,遍历 TreeMap 中的映射对儿,若某个映射值为0了,则直接跳过(表示该项在化简过程中完全抵消了),否则先将系数转为字符串加入到结果 res 中,然后就是要把变量都加在后面,遍历字符串数组,加一个星号,加一个变量即可,参见代码如下:

class Solution {
public:
    vector<string> basicCalculatorIV(string expression, vector<string>& evalvars, vector<int>& evalints) {
        vector<string> res;
        unordered_map<string, int> evalMap;
        for (int i = 0; i < evalvars.size(); ++i) {
            evalMap[evalvars[i]] = evalints[i];
        }
        auto t = helper(expression, evalMap);
        for (auto &a : t) {
            if (a.second == 0) continue;
            res.push_back(to_string(a.second));
            for (auto &p : a.first) res.back() += "*" + p;
        }
        return res;
    }
    
private:
    struct cmp {
        bool operator() (const vector<string>& a, const vector<string>& b) const {
            if (a.size() == b.size()) return a < b;
            return a.size() > b.size();
        }  
    };
    
    map<vector<string>, int, cmp> helper(string expr, unordered_map<string, int>& evalMap) {
        map<vector<string>, int, cmp> local{{{}, 1}}, res;
        string sub;
        for (int i = 0, sign = 1, n = expr.size(); i <= n; ++i) {
            if (i < n && expr[i] == ' ') continue;
            if (i < n && isalnum(expr[i])) sub += expr[i];
            else if (i < n && expr[i] == '(') {
                ++i;
                for (int cnt = 1; ; ++i) {
                    if (expr[i] == '(') ++cnt;
                    else if (expr[i] == ')') --cnt;
                    if (cnt == 0) break;
                    sub += expr[i];
                }
            } else { // '+', '-', '*' or i == n
                if (sub.size() == n) {
                    if (evalMap.count(sub)) return {{{}, evalMap[sub]}};
                    if (isdigit(sub[0])) return {{{}, stoi(sub)}};
                    return {{{sub}, 1}};
                }
                map<vector<string>, int, cmp> mult, t = helper(sub, evalMap);
                for (auto &a : local) {
                    for (auto &b : t) {
                        auto k = a.first;
                        k.insert(k.end(), b.first.begin(), b.first.end());
                        sort(k.begin(), k.end());
                        mult[k] += a.second * b.second;
                    }
                }
                local = move(mult);
                if (i == n || expr[i] != '*') { // '+' or '-'
                    for (auto &a : local) {
                        res[a.first] += sign * a.second;
                    }
                    sign = (i < n && expr[i] == '-') ? -1 : 1;
                    local = {{{}, 1}};
                }
                sub = "";
            }
        }
        return res;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/770

类似题目:

Basic Calculator III

Basic Calculator II

Basic Calculator

参考资料:

https://leetcode.com/problems/basic-calculator-iv/

https://leetcode.com/problems/basic-calculator-iv/discuss/113550/C++-relatively-concise-solution...

LeetCode All in One 题目讲解汇总(持续更新中…)


转载请注明来源于 Grandyang 的博客 (grandyang.com),欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 grandyang@qq.com

💰


微信打赏


Venmo 打赏

×

Help us with donation