给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 整数除法仅保留整数部分。
示例 1:
输入:s = "3+2*2"
输出:7
示例 2:
输入:s = " 3/2 "
输出:1
示例 3:
输入:s = " 3+5 / 2 "
输出:5
提示:
1 <= s.length <= 3 * 105s由整数和算符 ('+','-','*','/') 组成,中间由一些空格隔开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 春招闯关活动」, 点击查看 活动详情