需求:实现一个基本的计算器来计算简单的表达式字符串
说明:表达式字符串只包含非负整数, + - * / 操作符,左括号 ( ,右括号 ) 和空格。整数除法需要向下截断。
解法思路一:只有加号
解法:遇到数字就不断相加
// 1+2+10
const calculate = function(s) {
const sArr = s.split('+')
// 用一个队列数组存放
const q = [];
// 把表达式字符存放在队列中
for (let i = 0; i < sArr.length; i++) {
if (i !== sArr.length -1) {
q.push(sArr[i], '+')
} else {
q.push(sArr[i])
}
}
// 定义两个变量,num 用来表示当前的数字,sum 用来记录最后的和
let num = 0, sum = 0;
// 遍历数组队列
while(q.length) {
// 取出队列中的第一个字符
const c = q.shift()
// 是字符
if (isNaN(c - 0)) {
sum += num
num = 0
} else {
// 是数字: 如果数字字符是带有空格,也是不影响的,所以可以不用特殊处理空格字符
num = c - 0
}
}
// 最后一项是没有加号的,需要再加一次
sum += num
return sum
}
解法思路二:引入减号
解法:两个队列存放,一个队列存放数字,一个队列存放操作符
// 1+10-2
const calculate = function(s) {
// 存放数字的队列
const q = s.split(/[+-]/);
// 存放操作符的队列:
const op = s.split(/\d*/).filter(v => v.trim() !== '');
// 定义两个变量, sum 用来记录最后的和
let sum = q.shift() - 0;
// 遍历数字队列
while(q.length) {
// 取出操作符
const opV = op.shift()
// 取出下一个数字
const c = q.shift()
if (opV === '+') {
sum += c - 0
} else if (opV === '-') {
sum -= c - 0
}
}
return sum
}
解法思路三:引入乘除
解法:要考虑符号的优先级问题,不能再简单得对 sum 进行单向的操作。例如计算:1 + 2 * 10,当遇到乘号的时候:sum = 1,num = 2,而乘法的优先级比较高,得先处理 2 x 10 才能加 1。对此,就把它们暂时记录下来,具体操作如下
const calculate = function(s) {
// 存放数字的队列
const q = s.split(/[+-*/]/).filter(v => v);
// 存放操作符的队列:
const op = s.split(/\d*/).filter(v => v.trim() !== '');
// 用一个新的变量,记录要被处理的数, 设置一个初始项,为了当*或者/作为第一个操作符时计算:例如 2*10+2
const stack = [q.shift() - 0]
// 定义两个变量,sum 用来记录最后的和
let sum = 0;
// 遍历数字队列
while(q.length) {
// 取出操作符
const opV = op.shift()
// 取出下一个数字
const c = q.shift()
if (opV === '+') {
// 遇到加号,把当前的数压入到堆栈中
stack.push(c - 0)
} else if (opV === '-') {
// 减号:把当前数的相反数压入到堆栈中
stack.push(-c)
} else if (opV === '*') {
// 遇到乘法:取出stack中前一个数取出 和当前数相乘,把结果入栈中
stack.push(stack.pop() * c)
} else if (opV === '/') {
// 除号,把前一个数从堆栈中取出,然后除以当前的数,再把结果放回堆栈
stack.push(Math.floor(stack.pop() / c))
}
}
// 把堆栈中的数字求和
while(stack.length) {
sum += stack.shift()
}
// 返回最终结果
return sum
}
解题思路四:引入小括号
小括号的表达式优先计算:先利用上面的方法计算小括号里面的表达式;当遇到左括号的时候,可以递归的处理;当遇到右括号的时候,表明小括号里面的处理完毕,递归已经返回。
// 1+2*10+(3+4)=28
const calculate = function(s) {
// 存放数字的队列
const q = s.split(/[+-*/()]/).filter(v => v);
// 存放操作符的队列:
const op = s.split(/\d*/).filter(v => v.trim() !== '');
// 定义两个变量, sum 用来记录最后的和
let sum = 0;
return calculateRecursion(q, op, sum)
}
const calculateRecursion = function(q, op, sum) {
// 用一个新的变量,记录要被处理的数
const stack = [q.shift() - 0]
// 遍历数字队列
while(q.length) {
// 取出操作符
const opV = op.shift()
// 取出下一个数字
const c = q.shift()
// 遇到一个左括号,开始递归调用,求得括号里的计算结果,
if (opV === '(') {
// 将当前取出的值还放进队列中
q.push(c)
sum = calculateRecursion(q, op, sum)
} else {
if (opV === '+') {
// 遇到加号,把当前的数压入到堆栈中
stack.push(c - 0)
} else if (opV === '-') {
// 减号:把当前数的相反数压入到堆栈中
stack.push(-c)
} else if (opV === '*') {
// 遇到乘法:取出stack中前一个数取出 和当前数相乘,把结果入栈中
stack.push(stack.pop() * c)
} else if (opV === '/') {
// 除号,把前一个数从堆栈中取出,然后除以当前的数,再把结果放回堆栈
stack.push(Math.floor(stack.pop() / c))
} else if (opV === ')') {
// 遇到右括号,就可以结束循环,直接返回当前的总和
break
}
}
}
// 把堆栈中的数字求和
while(stack.length) {
sum += stack.shift()
}
return sum
}