1.题目
实现一个基本的计算器来计算一个简单的字符串表达式的值。
字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。
示例 1:
输入: "3+2*2"
输出: 7
示例 2:
输入: " 3/2 "
输出: 1
示例 3:
输入: " 3+5 / 2 "
输出: 5
说明:
(1)你可以假设所给定的表达式都是有效的。
(2)请不要使用内置的库函数 eval。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/basic-calculator-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2.我的题解
2.1逆波兰表达式
参见leetcode 224的解法,首先将中缀表达式转化为后缀表达式,再计算后缀表达式的值。当然这样的时间耗费和空间耗费都是挺大的。优点是通用。
class Solution {
stack<int> s1;
stack<string> s2;
char order[7][7] = {
// + - * / ( ) #
/*+*/ '>','>','<','<','<','>','>',
/*-*/ '>','>','<','<','<','>','>',
/***/ '>','>','>','>','<','>','>',
/*/*/ '>','>','>','>','<','>','>',
/*(*/ '<','<','<','<','<','=',' ',
/*)*/ ' ',' ',' ',' ',' ',' ',' ',
/*#*/ '<','<','<','<','<',' ','='
};
public:
int calculate(string s) {
vector<string> res=mid2post(s);
return evalRPN(res);
}
int evalRPN(vector<string>& tokens) {
stack<int>().swap(s1);
int len=tokens.size();
if(len==0){
return 0;
}
for(int i=0;i<len;i++){
if(tokens[i].length()==1 && (tokens[i][0]=='+' ||tokens[i][0]=='-' ||
tokens[i][0]=='/' || tokens[i][0]=='*')){
int num2 = s1.top();
s1.pop();
int num1 = s1.top();
s1.pop();
if(tokens[i][0]=='+')s1.push(num1+num2);
else if(tokens[i][0]=='-')s1.push(num1-num2);
else if(tokens[i][0]=='*')s1.push(num1*num2);
else if(tokens[i][0]=='/')s1.push(num1/num2);
}
else{
s1.push(stoi(tokens[i]));
}
}
return s1.top();
}
vector<string> mid2post(string s) {
vector<string> res;
stack<string>().swap(s2);//clear
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ')continue;
else if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/' || s[i]=='(') {
if (s2.empty()) {//第一次,直接入栈
s2.push(toString(s[i]));
}
else {//栈中运算符高的出栈
while (!s2.empty() && order[getOrder(s2.top()[0])][getOrder(s[i])] == '>') {
if(s2.top()!="(")res.push_back(s2.top());//直到遇到(
else break;
s2.pop();
}
s2.push(toString(s[i]));//当前符号入栈
}
}
else if (s[i] == ')') {
while (!s2.empty() && s2.top()!="(") {//直接出栈直到遇到(
res.push_back(s2.top());
s2.pop();
}
s2.pop();//"("
}
else {
int j = i + 1;
while (s[j] >= '0'&&s[j] <= '9')j++;
res.push_back(s.substr(i, j - i));
i=j-1;//for循环i自己会++,所以i=j-1
}
}
while (!s2.empty()) {//最后不要忘了清空栈
res.push_back(s2.top());
s2.pop();
}
return res;
}
int getOrder(char s) {
if (s == '+')return 0;
else if (s == '-')return 1;
else if (s == '*')return 2;
else if (s == '/')return 3;
else if (s == '(')return 4;
else if (s == ')')return 5;
else if (s == '#')return 6;
return -1;
}
string toString(char ch) {//单个字符转字符串
string res = "a";
res[0] = ch;
return res;
}
};
2.2 双栈法
class Solution {
stack<int> s1;
stack<char> s2;
public:
int calculate(string s) {
for(int i=0;i<s.size();i++){
if(s[i]==' ')continue;
if(s[i]=='+' || s[i]=='-'){
while(!s2.empty())calc();
s2.push(s[i]);
}
else if(s[i]=='*' || s[i]=='/'){
while(!s2.empty() && (s2.top()=='*' || s2.top()=='/'))calc();
s2.push(s[i]);
}
else{
int j=i+1;
while(s[j]>='0' && s[j]<='9')j++;
s1.push(stoi(s.substr(i,j-i)));
i=j-1;
}
}
while(!s2.empty())calc();
return s1.top();
}
int getValue(int num1,int num2 ,char ch){
if(ch=='+')return num1+num2;
else if(ch=='-')return num1-num2;
else if(ch=='*')return num1*num2;
else if(ch=='/')return num1/num2;
return 0;
}
void calc(){
int num2=s1.top();s1.pop();
int num1=s1.top();s1.pop();
s1.push(getValue(num1,num2,s2.top()));
s2.pop();
}
};
3.别人的题解
上面两种解法的时间耗费和空间耗费都比较高,分析一下开销主要在以下方面:
(1)栈的操作:STL库本来就慢;
(2)string.substr(int start,int len),stoi(string s)
;
(3)函数的调用;
一个时间和空间相对较优的实现如下:
class Solution {
public:
int calculate(string s) {
long res = 0, curRes = 0, num = 0, n = s.size();
char op = '+';
for (int i = 0; i < n; ++i) {
char c = s[i];
if (c >= '0' && c <= '9') {
num = num * 10 + c - '0';
}
if (c == '+' || c == '-' || c == '*' || c == '/' || i == n - 1) {
switch (op) {
case '+': curRes += num; break;
case '-': curRes -= num; break;
case '*': curRes *= num; break;
case '/': curRes /= num; break;
}
if (c == '+' || c == '-' || i == n - 1) {
res += curRes;
curRes = 0;
}
op = c;
num = 0;
}
}
return res;
}
};
4.总结与反思
(1)for循环中尽量避免改变循环变量的值,一般都有办法一个一个读,使用变量记录即可。(如本题找一个完整的数,可以用一个int变量记录)
(2)为了时空间耗费最优,少调用花里胡哨的函数。