实现一个基本的计算器来计算一个简单的字符串表达式 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 * 105s由数字、'+'、'-'、'('、')'、和' '组成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 春招闯关活动」, 点击查看 活动详情