基本计算器 II|刷题打卡

254 阅读2分钟

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 整数除法仅保留整数部分。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 1 <= s.length <= 3 * 105
  • s 由整数和算符 ('+', '-', '*', '/') 组成,中间由一些空格隔开
  • s 表示一个 有效表达式
  • 表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1]
  • 题目数据保证答案是一个 32-bit 整数

解题思路

这道题和基本计算器这道题类似,同样是做字符串算式的运算。区别在于去掉了括号,加入了乘除法。难点同样在于 */ 要优先运算的运算法则。

考虑输入,每个字符由数字字符,运算符字符以及空格组成。抛开空格的情况,有两种情况:

  • 如果字符是个数字,那就可能是在继续读取某个运算的数字,需要把有可能多位的数字读取完。
  • 如果是运算符,代表前面一个数字已经读取完了。

所以我们可以定义一个判断当前字符是否是数字字符的方法,然后遍历整个字符串:

var calculate = function(s) {
  let strNum = '';
  
  const  isNum = (c) =>  Number(c) === Number(c);
  
  for (let i=0;i<s.length;i++){
    const c = s[i];
    if(isNum(c)) strNum+=c;
    if((!isNum(c) && c !==' ') || i === s.length-1) {
      //TODO something
    }
  }

  return // return something
};

这里用了 JS 中 NaN!==NaN 这个梗来判断,当然有更简单的 isNan() 方法。

如果是数字,就加到我们这个临时的数字字符串中,注意是因为 JS 中字符串的相加是粘贴在字符串之后。另外还考虑了最后一个字符的情况,应当同样归到“遇到了一个运算符”这个情况里处理。

接下来处理遇到运算符或者到字符串末尾的情况。我们知道在这种情况下,代表了一个运算数字被读取完成。那么在这个情况下,会遇到两种情况:

  • 这个数字之前的运算符是 * 或者 /,那么就需要优先用这个数字和之前的一个数字做运算。
  • 这个数字之前的运算符是 + 或者 - 那么就之后再做运算。

这样我们的问题就简化成了:需要在一个数字读取完成时,找到之前的运算符,以及前一个数字。 想到这里,这个数据结构就呼之欲出了。

定义和代表之前运算符的状态。

var calculate = function(s) {
  const stack = [];
  let strNum = '';
  let sign = '+';
  
  const  isNum = (c) =>  Number(c) === Number(c);
  
  for (let i=0;i<s.length;i++){
    const c = s[i];
    if(isNum(c)) strNum+=c;
    if((!isNum(c) && c !==' ') || i === s.length-1) {
      const num = Number(strNum);
      if(sign === '+') {
        //TODO
      } 
      else if(sign === '-') {
        //TODO
      }
      else if(sign === '*') {
        //TODO
      } 
      else if(sign === '/') {
        //TODO
      }
      sign = c;    //保存上一个运算符
      strNum = '';
    }
  }

  return // return something
};

注意在这,我们需要把字符串的运算数字转换成真正的数字,并且在完成运算后清空字符串运算数。

首先考虑 +- 的情况,在这两种情况下,并不优先计算,所以只需要把运算数压入栈内就好。

var calculate = function(s) {
  const stack = [];
  let strNum = '';
  let sign = '+';
  
  const  isNum = (c) =>  Number(c) === Number(c);
  
  for (let i=0;i<s.length;i++){
    const c = s[i];
    if(isNum(c)) strNum+=c;
    if((!isNum(c) && c !==' ') || i === s.length-1) {
      const num = Number(strNum);
      if(sign === '+') {
        stack.push(num);
      } 
      else if(sign === '-') {
        stack.push(-num);
      }
      else if(sign === '*') {
        //TODO
      } 
      else if(sign === '/') {
        //TODO
      }
      sign = c;    //保存上一个运算符
      strNum = '';
    }
  }

  return // return something
};

注意如果是 -,压入的就是负值。

接下来就是 * 或者 / 的情况。根据前面的分析,我们需要把用上一个数字和当前数字作运算,也就是说把栈顶的数字替换成运算的结果

var calculate = function(s) {
  const stack = [];
  let strNum = '';
  let sign = '+';
  
  const  isNum = (c) =>  Number(c) === Number(c);
  
  for (let i=0;i<s.length;i++){
    const c = s[i];
    if(isNum(c)) strNum+=c;
    if((!isNum(c) && c !==' ') || i === s.length-1) {
      const num = Number(strNum);
      if(sign === '+') {
        stack.push(num);
      } 
      else if(sign === '-') {
        stack.push(-num);
      }
      else if(sign === '*') {
        stack.push(stack.pop() * num);
      } 
      else if(sign === '/') {
        stack.push(parseInt(stack.pop() / num));
      }
      sign = c;    //保存上一个运算符
      strNum = '';
    }
  }

  return // return something
};

因为题里要求除法只保留整数部分,所以在处理除法的时候用了 parseInt() 方法.

这样,就计算完了所有的乘除法(其实包括减法,把减号当作负数来考虑)。接下来只要把栈里的值加起来就好了,完整代码:

var calculate = function(s) {
  const stack = [];
  let strNum = '';
  let sign = '+';
  
  const  isNum = (c) =>  Number(c) === Number(c);
  
  for (let i=0;i<s.length;i++){
    const c = s[i];
    if(isNum(c)) strNum+=c;
    if((!isNum(c) && c !==' ') || i === s.length-1) {
      const num = Number(strNum);
      if(sign === '+') {
        stack.push(num);
      } 
      else if(sign === '-') {
        stack.push(-num);
      }
      else if(sign === '*') {
        stack.push(stack.pop() * num);
      } 
      else if(sign === '/') {
        stack.push(parseInt(stack.pop() / num));
      }
      sign = c;    //保存上一个运算符
      strNum = '';
    }
  }

  return stack.reduce((sum, num) => sum+num, 0);
};

总结

遇到需要和迭代顺序不一样,需要优先处理的情况。可以分步骤考虑遇到的几种情况,分别需要做什么处理,以及需要哪些元素。这样就可以选用合适的数据结构来支持解决问题,提高解决问题的效率。

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