227. 基本计算器 II - 力扣(LeetCode)
发布:2021年9月28日22:46:27
问题描述及示例
给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
示例 1:
输入:s = “3+2*2”
输出:7
示例 2:
输入:s = " 3/2 "
输出:1
示例 3:
输入:s = " 3+5 / 2 "
输出:5
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/basic-calculator-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
提示:
1 <= s.length <= 3 * 105
s 由整数和算符 (’+’, ‘-’, ‘*’, ‘/’) 组成,中间由一些空格隔开
s 表示一个 有效表达式
表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1] 内
题目数据保证答案是一个 32-bit 整数
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/basic-calculator-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我的题解
成功前的尝试(暴力解法)
首先是利用 Array.split()
函数和 map()
函数来生成一个数组类型的对象 splitedStr
,该数组中存储了经过修剪整理的 s
数据。大体的效果如下图所示:
注意,如果某个数字字符串的前后有空格,
parseInt()
函数也能正确地将其转换为有效的整数。如下图所示:
接下来的第一个 while
循环就是执行 splitedStr
中的所有的 *
或 /
运算。
第二个 while
循环则是完成剩下的所有 +
或 -
运算,最后 splitedStr
中将只剩下一个元素,该元素即为 s
表达式所得到的结果,将其返回即可。
/**
* @param {string} s
* @return {number}
*/
var calculate = function(s) {
// 生成一个过滤后的数组,该数组中的元素大致就是上面图1的结构
let splitedStr = s.split(/(\+|-|\*|\/)/).map(
cur => isNaN(parseInt(cur)) ? cur : parseInt(cur)
);
// temp是临时变量,用于存储某两个数字直接的运算结果
let temp = 0;
// index 用于第一个while循环中标识当前遍历元素的下标
let index = 0;
// 第一个while循环用于完成 splitedStr 中所有乘法或除法的运算
while(index < splitedStr.length) {
// 如果当前元素是乘或除,则将该元素的前驱元素和后继元素先做相应的运算,
// 并将结果覆盖这三个元素,注意这里用到了 splice 函数,所以数组的长度会改变
// 所以下方有一个 index-- 的操作以防止数组长度塌陷
if(["*", "/"].includes(splitedStr[index])) {
temp = splitedStr[index] === "*" ? splitedStr[index-1] * splitedStr[index+1] : parseInt(splitedStr[index-1] / splitedStr[index+1]);
splitedStr.splice(index-1, 3, temp);
// index-- 用于防止出现数组长度塌陷
index--;
}
index++;
}
// 第二个while循环用于完成 splitedStr 中所有加法或减法的运算
while(splitedStr.length > 1) {
// 注意由于此时 splitedStr 中只剩下加法和减法,所以直接针对前三位进行操作即可
temp = splitedStr[1] === "+" ? splitedStr[0] + splitedStr[2] : splitedStr[0] - splitedStr[2];
splitedStr.splice(0, 3, temp);
}
// 最终结果都将累积到一个元素身上,将其返回即可
return splitedStr[0];
};
提交记录
108 / 109 个通过测试用例
执行结果:超出时间限制
最后执行的输入:"1+7-7+3+3+6-3+1-3...6-9-9+0+6+4+2+7+1-4-6-6-0+6+3-7"
时间:2021/09/28 22:51
如果不计算创建 splitedStr
时的那次遍历,那么在最坏的情况下(即:s
中所有的运算符都是 +
或 -
),上面的程序将会完整地遍历 splitedStr
两次,而且上面的 splice()
函数是直接操作 splitedStr
,所以会移动大量的元素,这就导致当遇到非常长的 s
时,程序的时间花费非常大。
我的题解1(栈)
由于上面的程序性能太差,没法儿通过提交,所以我想到了改进的方法:用栈来累积存储 splitedStr
中的计算结果。
其实一开始我想到的就是用栈结构来做辅助,但是用的是两个栈:用
numbers
栈用于存储s
中的数据,而用operators
栈来存储运算符。但是后来发现这种解法会遇到大量的判断情况从而导致程序非常臃肿,所以就中途舍弃了,然后转而想出了上面那种超时的做法,所以我现在也不确定利用双栈的做法是否可行。
首先和上面一样,先生成 splitedStr
数组,然后逐位遍历 splitedStr
中的元素。
- 如果当前元素是数字,则直接压入
result
栈中; - 如果当前元素是运算符
*
或/
中的一个,则将result
的栈顶元素resultTop
弹出,然后用栈顶元素resultTop
和当前遍历元素的下一个元素splitedStr[i+1]
(这个元素一定是数字)做相应的*
或/
运算,然后将运算结果作为新的栈顶元素压入result
栈; - 如果当前元素是运算符
+
或-
中的一个,先将当前元素压入result
栈中,然后再判断:- 如果此时
result
栈中没有+
或-
运算符,那么就继续往下遍历; - 如果此时
result
栈中已经有了一个+
或-
运算符,那么就要将result
栈中存储的前三位(此时这前三位一定是[数字, 加或减运算符, 数字]
的顺序)进行一次运算,并且把运算结果覆盖掉这前三位。
我是通过
count
这个专门的计数变量来判断result
栈中是否已经有了一个+
或-
运算符的,当然还可以通过indexOf()
或includes()
等函数来判断。
- 如果此时
需要特别注意到的是,因为 result
栈中的结果会动态累积,所以其栈长度最大为 4
。
/**
* @param {string} s
* @return {number}
*/
var calculate = function(s) {
// splitedStr的作用和上面一样
let splitedStr = s.split(/(\+|-|\*|\/)/).map(
cur => isNaN(parseInt(cur)) ? cur : parseInt(cur)
);
// result用于存储累积的结果和运算符,其长度最大为4
let result = [];
// resultTop用于存储result的栈顶的有效累积数字和
let resultTop = 0;
// count用于记录当前result栈中有几个加号或减号
let count = 0;
// 开始遍历 splitedStr
for(let i = 0; i < splitedStr.length; i++) {
// 如果当前元素是数字,直接压入result栈即可,并结束本次循环
if(typeof splitedStr[i] === 'number') {
result.push(splitedStr[i]);
continue;
}
// 如果当前元素是乘号或除号,
if(["*", "/"].includes(splitedStr[i])) {
// 则取出result栈顶元素,
resultTop = result.pop();
// 并与后一个数字进行相应的乘或除运算
resultTop = splitedStr[i] === "*" ? resultTop * splitedStr[i+1] : parseInt(resultTop / splitedStr[i+1]);
// 并将重新计算的结果作为栈顶元素压入result
result.push(resultTop);
// 同时将i指针往后移动一位,因为上面的操作相当于已经遍历了当前元素的后一位
i++;
// 结束本次循环
continue;
}
// 如果当前元素既不是数字,也不是乘号或除号,则一定是加号或减号,将其压入result栈
result.push(splitedStr[i]);
// 此时计数器加一,表示当前result栈中的加号或减号数量加了1
count++;
// 如果此时count累加到了2,则说明此前result栈中已经有了一个加号或减号
if(count === 2) {
// 将result中的前三位进行相应的运算
resultTop = result[1] === "+" ? result[0] + result[2] : result[0] - result[2];
// 将上面获得的结果覆盖掉result的前三位
result.splice(0, 3, resultTop);
// 此时result中只有一个加号或减号
count = 1;
}
}
// 最后这里和上面的有点不同,因为这种解法下,result最后可能会有两种情况:
// 1、result中只有一个元素,则该元素即为 s 表达式的最终值(所有运算的累积值)
if(result.length === 1) {
return result[0];
}
// 2、result中有三个元素,分别是[数字, 加或减运算符, 数字],此时还要将其整合为最后结果
return result[1] === "+" ? result[0] + result[2] : result[0] - result[2];
};
提交记录
109 / 109 个通过测试用例
状态:通过
执行用时:104 ms, 在所有 JavaScript 提交中击败了24.08%的用户
内存消耗:49.5 MB, 在所有 JavaScript 提交中击败了5.14%的用户
时间:2021/09/29 00:21
折腾了好久,总算是通过了,但是空间表现似乎不怎么好……
官方题解
更新:2021年7月29日18:43:21
因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。
更新:2021年9月28日15:03:57
【更新结束】
有关参考
更新:2021年9月28日22:54:13
参考:String.prototype.split() - JavaScript | MDN
参考:Array.prototype.map() - JavaScript | MDN