这天遇上了一个奇奇怪怪的题目,要你完成一个计算器,题目链接https://leetcode-cn.com/problems/basic-calculator/
这个题目的意思很清晰,就是给你一个字符串,让你算完之后返回结果。与简单的题目不同的是,这个字符串中会有空格,我们应该如何处理。更难的地方是这个题目它有一个括号,关于括号它的优先级是最高的,我们虽然自己知道这个规则,但是告诉计算机这个规则的话,可能并不是这么容易。
所以呢,在解决掉这道题之前,我们先解决一个简单一点的题目:leetcode227:基本计算器2,题目链接https://leetcode-cn.com/problems/basic-calculator-ii/
这道题和前面的区别就是没有括号,我们处理起来会比较简单一些。但是会多乘号和除号,不过和括号的难度比起来不足为惧。
写这道题时,我们需要深刻理解到运算符的规则,例如 5-4+1 它的本质是什么,其实就是(+5)+(-4)+(+1),加减运算中我们都可以使用加号来表示,把这些数全部加起来,只不过减号和加号的区别就是减号就是加上这个数的负数,所以我们可以声明一个栈,把这些数字逐一放入栈里面,全部相加就是最后我们的结果。例如5-4+1 我们栈中就依次放入5,-4,1。换句话说,就是把所有的运算符换成加号的形式。
那么如果这个数不是一位数,而是一个多位数的话该怎么办?很简单,我们只要继续看s[i+1],然后当前的数乘以10加上它就好了,就比如53,当前cur=5,我们下面的数是3,那么这个数就是cur*10+3=53。
值得一提的是,由于给我们的是一个字符串,我们每次取出来的数是ASCII码,因此每个数都应该减去’ 0 '这个字符对应的ASCII码。(0~9这九个数在ASCII码中是连续的)例如1的ASCII码是49,那么这个数就是s[i]-‘0’=49-48=1,关于这种减去ASCII码我们需要注意整形溢出的情况,因为cur*10+s[i]-‘0’我们是先加上s[i],s[i]>=48,如果本身我们的cur乘以10已经非常接近INT_MAX了,这个时候再加上s[i]很可能就会导致溢出,所以我们应该先减再加。也就是cur乘以10+(s[i]-‘0’)。
例如下面这个测试用例
那么乘号和除号我们该怎么解决?很简单,由于+和-他们只是管自己,而乘和除它们是和上一个数有关,例如3+5*2,在到2的时候,此时我们栈中是[3,5],而当前元素是2,我们就取出栈顶的元素5,和当前元素进行相乘,得到的结果再放入栈中,此时栈中元素就是[3,10],最后全部相加就是我们想要的结果啦。
先看代码
class Solution {
public:
int calculate(string s) {
int n=s.size();
char flag='+';
stack<int> t;
int cur=0;
for(int i=0;i<n;i++){
if(s[i]<='9'&&s[i]>='0')
cur=cur*10+(s[i]-'0');
if(i==n-1||((s[i]>'9'||s[i]<'0')&&s[i]!=' ')){
if(flag=='+')
t.push(cur);
else if(flag=='-')
t.push(-cur);
else if(flag=='*'){
int now=t.top();
t.pop();
t.push(cur*now);
}
else{
int now=t.top();
t.pop();
t.push(now/cur);
}
cur=0;
flag=s[i];
}
}
int ret=0;
while(!t.empty()){
ret+=t.top();
t.pop();
}
return ret;
}
};
在这个代码中,我用flag来记录上一次出现的运算符号是什么,刚开始的时候呢,我们就默认为+,假如s[i]是数字的话我们就cur=cur*10+(s[i]-‘0’)让我们当前的数字变大,如果不是数字并且不等于空格的话(意思就是这个位置是一个运算符)那我们就进行运算,如果flag是加号的话就把当前的数放入栈,如果是减号的话就把当前的数的负数放入栈,如果是乘号和除号的号话就拿出栈顶然后和当前的数进行运算后将结果放回栈中。最后更新一下我们的flag就行啦。
值得一提的是,当当前的i已经到达了字符串s的末尾的时候我们也要进行一次运算,所以if(i==n-1||((s[i]>‘9’||s[i]<‘0’)&&s[i]!=’ '))不能变成else if,这是因为最后一个是数字的话,我们就少了一次运算。比如2*5,到5这个位置的时候,栈中的元素是2,flag=乘,cur=5,如果我们加上了else的话最后一次运算就少掉了,就会少了拿出2乘以5=10再放入栈中这个操作。最后的答案也不会正确。
最后我再用一个例子来模拟这道题,例如s=" 3+5 / 2 "。
1)i=0,s[i]=’ ’ ,此时cur=0,flag=+;
2)i=1,s[i]=‘3’,此时cur=3,flag=+;
3)i=2,s[i]=’+’,我们就将3放入栈中,cur=0,flag=+;
4)i=3,s[i]=’ 5’ ,此时cur=5,flag=+;
5)i=4,s[i]=’ ’ ,此时cur=5,flag=+;
6)i=5,s[i]=’/ ’ ,我们就将5放入栈中,cur=0,flag=/;
7)i=6,s[i]=’ ’ ,此时cur=0,flag=/;
8)i=7,s[i]=’ 2’ ,此时cur=2,此时i==s.size()-1了,因为flag=/,所以我们将栈顶元素5拿出除以2得到2放入栈中
最后遍历整个栈我们将所有元素相加3+2=5,就是我们想要得到的答案。
至此,leetcode227道题就已经讲完了,我们现在需要考虑如果有括号我们该如何处理。
那我们就想啊,由于只要有括号,无论如何我就都要先算括号里面的东西,所以只要我们遇到左括号,我们就将之前算的结果放入栈里面。我们继续想啊,例如5-(1+2),那么这个括号里面算出来的数到最后其实是要取反的,因为他前面是减号,所以我们栈里面放进去的不仅仅是之前算的结果5,还要放入一个负号,来记录一下在算括号里面的算术时,最后算完应该是取反还是不取反。在遇到右括号的时候呢,我们就把当前算完的结果3取负,再拿出栈中的元素5相加,就得到最后的结果2了。
下面是代码
class Solution {
public:
int calculate(string s) {
int n=s.size();
int op=1;
int ret=0;
stack<int> t;
for(int i=0;i<n;i++){
if(s[i]==' ')
continue;
if(s[i]=='('){
t.push(ret);
t.push(op);
op=1;
ret=0;
}
else if(s[i]==')'){
op=t.top();
t.pop();
ret=ret*op+t.top();
t.pop();
}
else if(s[i]=='+')
op=1;
else if(s[i]=='-')
op=-1;
else{
int cur=0;
while(i<n&&s[i]>='0'&&s[i]<='9'){
cur=cur*10+(s[i]-'0');
i++;
}
i--;
ret+=cur*op;
}
}
return ret;
}
};
我使用op这个数来表示括号中的算术算完后是取正还是取负,之所以不使用bool类型是因为我们要乘上括号内的结果,就可以少一些判断语句,就比如3-(5-2),括号中算完后是3,由于op=-1,我们就需要把3*(-1)然后加上栈中的3。
和上面做法的区别呢就是我们遇到数字的时候,就直接把这个数字是计算出来,这是因为我们算完后就马上要与ret进行相加。
接下来我再用s="11+(4 +5-2)-(6+8)"这个例子来模拟一下这个过程。
1)i=0,此时op=1,ret=0,cur=1,i++,cur=11,ret=0+11=11;
2)i=2,此时s[i]=+,op=1;
3)i=3,遇到左括号栈中放入11,再放入状态1,ret=0,op=1;
4)i=4,此时cur=4,ret=0+4=4;
5)i=5,此时s[i]=’ ',continue;
6)i=6,此时s[i]=+,op=1;
7)i=7,此时cur=5,ret=4+5=9;
8)i=8,此时s[i]=-,op=-1
9)i=9,此时cur=2,ret=9+(-1)*2=7;
10)i=10,遇到右括号,我们先拿出栈顶元素1,7乘以1=7,再拿出栈顶元素,ret=7+11=18;
11)i=11,此时s[i]=-,op=-1;
12)i=12,遇到左括号栈中放入18,再放入状态-1,ret=0,op=1;
13)i=13,此时cur=6,ret=0+6=6;
14)i=14,此时s[i]=+,op=1;
15)i=15,此时cur=8,ret=6+8=14;
16)i=16,遇到右括号,我们先拿出栈顶元素-1,14乘以(-1)=-14,再拿出栈顶元素,ret=18+(-14)=4;
所以最后的结果就是4,至此,这道题的细节我也已经讲清楚啦,喜欢的话欢迎点赞评论哦。
继续加油:)