计算器问题大约在大二数据结构的课程上学过,当时是使用栈来解决,此外还需要设置很多符号的优先级,以此判断是否弹栈,代码写的很长很麻烦。今天又遇到了这种题,也有了简单的解法,做一个整理。
计算器的难点在于先算乘除后算加减,如果有小括号,需要先算括号里面的内容。因此从简往难,一点点的来看问题如何解决。
无括号
无括号相对简单一些,我们只需要考虑先算乘除后算加减即可。那么思路就是:如果当前符号是加法或减法,那么将符号连带数字压入栈中,比如 +10 或 -11;如果遇到的是乘法或除法,那么就需要把栈尾的元素拿出来,做乘法或除法运算在放入栈中。最后,对栈内的元素求和即可。
我们来看程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
   | class Solution { public:     int calculate(string s) {         int idx = 0;         return subcal(s, idx);     }     int subcal(string s, int& idx) {         int res = 0;         stack<int> stk;         int num = 0;         char sign = '+';         for (int i = 0; i < s.size(); i++) {             char c = s[i];             if (isdigit(c)) {                 num = 10 * num + (c - '0');             }              if (!isdigit(c) && c != ' ' || i == s.size() - 1) {                 if (sign == '+') {                     stk.push(num);                 } else if (sign == '-') {                     stk.push(-num);                 } else if (sign == '*') {                     auto tmp = stk.top();                     stk.pop();                     stk.push(tmp * num);                 } else if (sign == '/') {                     auto tmp = stk.top();                     stk.pop();                     stk.push(tmp / num);                 }                 sign = c;                 num = 0;             }         }         while (stk.size()) {             res += stk.top();             stk.pop();         }         return res;     } };
  | 
 
程序中有很多的细节,一点点来分析:
sign 初始值为 +,这样如果开头的数字是正数,那么这个数会被放入栈中;如果开头的数字是负数,那么栈中先放入 0,在放入负数,不影响。 
- 符号 
c 的判断不构成 if-else-if 关系,因为当 i=s.size()-1 的时候,需要处理最后一个数字。即,c 是数字且是最后一个字符的情况下,需要经过这两个分支的处理。 
有括号
右括号就比较烦人,因为括号的优先级高于一切,需要找到最内部的括号,逐层往外回退得到答案。而这,也让人容易联想到传说中的递归。
那么递归需要记录关于括号的哪些东西呢?想法自然是遇到一个左括号,那么就计算括号内部的东西,遇到右括号返回。之后跳过括号里面的内容,计算下一个表达式。括号中的括号也是同理。我们来看程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
   | class Solution { public:     int calculate(string s) {         int idx = 0;         return subcal(s, idx);     }     int subcal(string s, int& idx) {         int num = 0;         char sign = '+';         stack<int> stk;         for (int i = 0; i < s.size(); i++) {             char c = s[i];             if (isdigit(c))                 num = 10 * num + (c - '0');             if (c == '(') {                 num = subcal(s.substr(i+1), idx);                 i += (idx + 2);                 c = s[i];             }             if (!isdigit(c) && c != ' ' || i == s.size() - 1) {                 if (sign == '+')                     stk.push(num);                 else if (sign == '-')                     stk.push(-num);                 else if (sign == '*') {                     auto tmp = stk.top();                     stk.pop();                     stk.push(num * tmp);                 }                 else if (sign == '/') {                     auto tmp = stk.top();                     stk.pop();                     stk.push(tmp / num);                 }                 sign = c;                 num = 0;             }             if (c == ')') {                 idx = i;                 break;             }         }         int res = 0;         while(stk.size()) {             res += stk.top();             stk.pop();         }         return res;     } };
  | 
 
同样来解读一下:
- 如果遇到右括号,记录索引,表示当前括号的内容计算完了,退出。
 
- 如果遇到左括号,那么计算左括号后面子串的内容得到数字,
i=i+(idx+2) 的意思是,跳到右括号后面第一个字符继续计算,+2 为什么是后面第一个字符呢?因为 substr(i+1) 了,这里向后移动了一位,对于 i 来说,+2 才是后面的第一位。