基本计算器|刷题打卡

319 阅读2分钟

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

示例 1:

输入:s = "1 + 1"
输出:2
示例 2:

输入:s = " 2-1 + 2 "
输出:3
示例 3:

输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23

提示:

  • 1 <= s.length <= 3 * 105
  • s 由数字、'+''-''('')'、和 ' ' 组成
  • s 表示一个有效的表达式

解题思路

拿到这个题目,感觉比较困难的地方就是要让计算机符合人类计算的方式,从来逐渐找到计算的方式。可能处理小括号是一个比较麻烦的地方。

思考一下,发现每次计算,都需要先找到最响铃的封闭 () 中的表达式进行计算,然后再去寻找外层的封闭 ()。于是每次寻找时,封闭 () 就成了一种标识,利用正则表达来寻找就是再合适不过的了。

找到每个表达式以后就简单了,只需要计算括号中只有 +- 表达式,然后将结果替换回去,再接着计算,直到所有的 () 都消除干净,就可以按顺序计算了。

首先就写出计算没有括号的情况下的表达式的函数:

 function calcPlainStr(s) {
    const reg = /[+\-]*[0-9]+/g;
    const matches = s.match(reg);
    if(!matches) return 0;
    return matches.reduce((sum, strNum) => {
      if(strNum[0] === '+') sum+=parseInt(strNum.slice(1));
      else if(strNum[0] === '-') sum-=parseInt(strNum.slice(1));
      else sum += parseInt(strNum);
      return sum;
    }, 0)
  }

这里比较复杂的在于,字符串的数字可能是多位数,为了通用处理多位数的情况,这里利用正则表达式 /[+\-]*[0-9]+/g 将字符串分割成了数字或者符号加数字的数组。

在这里对列表中的每个元素,判断第一个字符是不是符号,并进行对应的运算操作就好了,这样,我们就可以计算每组括号内的表达式以及没有括号的表达式了。

接下来,就是将有括号的表达式带进来,通过正则 /\(([0-9][+\-]*)*\)/g 可以达到上面我们所说的找出每组相邻括号内的表达式。

只需要将每组找出来的表达式进行运算,并替换回去,再用新的字符串重新匹配,直到所有括号的消除干净,就能得到我们的结果了:

  function calcAll(s) {
    const reg = /\(([0-9][+\-]*)*\)/g;
    const matches = s.match(reg);
    if (!matches) return calcPlainStr(s);
    matches.forEach(v => s = s.replace(v, calcPlainStr(v)));
    return calcAll(s);
  }

最后,因为字符串中可能包含空格字符 ' ',会影响正则的匹配,而空格又不参与计算,所以提前把它过滤掉:

  const filtedS = s.split('').filter(c => c!==' ').join('');

任务完成!完整代码如下:

/**
 * @param {string} s
 * @return {number}
 */
var calculate = function(s) {
  const filtedS = s.split('').filter(c => c!==' ').join('');

  function calcPlainStr(s) {
    const reg = /[+\-]*[0-9]+/g;
    const matches = s.match(reg);
    if(!matches) return 0;
    return matches.reduce((sum, strNum) => {
      if(strNum[0] === '+') sum+=parseInt(strNum.slice(1));
      else if(strNum[0] === '-') sum-=parseInt(strNum.slice(1));
      else sum += parseInt(strNum);
      return sum;
    }, 0)
  }

  function calcAll(s) {
    const reg = /\(([0-9][+\-]*)*\)/g;
    const matches = s.match(reg);
    if (!matches) return calcPlainStr(s);
    matches.forEach(v => s = s.replace(v, calcPlainStr(v)));
    return calcAll(s);
  }

  return calcAll(filtedS);
};

总结

对于复杂字符串的处理,如果能找到对应的匹配规则,使用正则表达式去匹配,并递归逐层处理是一种比较简单的方法。不过大量的正则匹配,会有大量的遍历,会比较消耗时间。

对于这道题,还可以通过一次遍历来处理括号的方式来计算,因为括号总是要成对出现才计算,可以考虑通过的数据结构暂存表达式,再在合适的时候出栈计算。类似于函数的调用栈方式,遇到( 的时候调用新函数并传参,遇到 ) 的时候返回,需要特别考虑的地方在于多位数的处理方式。就不再赘述了。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情