题目链接:
面试题 16.26. 计算器
227. 基本计算器 II
两个题都用中缀转后缀进行计算,无非就是第二题多了括号,在此之前,先明白两个概念操作数和操作符:操作数,就是指表达式中的数字,而操作符指的是表达式中的符号如+、-、* 、/、(、[、]、)等。
先回顾下中缀转后缀的流程:对于一个中缀表达式:a + b * c + (d * e + f ) * g
我们一般采用基于栈的方式进行转换,这里会用到两个栈,操作数栈和操作符栈,这一点《数据结构》中也有讲过:
1、首先从左到右扫描每一个字符。如果扫描到的字符是操作数(如a、b等),就直接将操作数push进操作数栈中。
2、如果扫描到的字符是一个操作符,分三种情况:
(1) 如果操作符栈是空的,直接将操作符push到操作符栈中。
(2) 如果该操作符的优先级大于操作符栈栈顶的操作符,就直接将操作符push到操作符栈中。(因为该操作符运算会比栈中的优先级低的先进行运算)
(3)如果该操作符为+、-、*、/等且优先级低于栈顶的操作符,就将栈顶的操作符pop, 直到该操作符的优先级大于栈顶端的操作符,将扫描到的操作符push到栈中。
3、如果遇到的操作符是左括号"(”,就直接将该操作符push到操作符栈中。
4、如果扫描到的操作符是右括号“)”,将操作符栈中的操作符(pop)且操作数栈中出栈两个元素进行运算,运算完之后再push回操作数栈,直到遇见左括号“(”,最后将操作符栈中的左括号pop掉。
5、如果输入的中缀表达式已经扫描完了,但是操作符栈中仍然存在操作符的时候,我们应该将操作符栈中的操作符pop,然后进行操作数栈的运算。
注意:在编码过程中,为了方便一般会在整个表达式的左右两边分别加上左括号和右括号,这样可以规避较多的情况,比如刚开始操作符入栈时栈中没有元素无法进行比较、再比如最后中缀表达式遍历完了之后操作符栈中还有元素。
总的来说:中缀表达式便于人们的理解与计算,但是后缀表达式更方便计算机的运算(如二叉树、堆栈的方法计算),因此在读取一个中缀表达式后,我们得办法将其转化为后缀表达式。
理解了上面流程,那么对于上面两个题的代码就理解到了,详情看代码注释。
class Solution {
Stack<Character> cStack=new Stack<>(); //操作符栈
Stack<Integer> iStack=new Stack<>(); //操作数栈
public int level(char c){ //获取操作符优先级,(优先级最低,可直接入栈
switch(c){
case '+': return 1;
case '-': return 1;
case '*': return 2;
case '/': return 2;
case '(': return 0;
}
return 0;
}
public void popStackCompute(){ //出栈计算
char c=cStack.pop();
int num2=iStack.pop(); //注意出栈和运算的顺序,先入栈的在前
int num1=iStack.pop();
switch(c){
case '+':
iStack.push(num1+num2);
break;
case '-':
iStack.push(num1-num2);
break;
case '*':
iStack.push(num1*num2);
break;
case '/': //这里实际上考虑还会考虑是否除0的情况
iStack.push(num1/num2);
break;
}
}
public int calculate(String s) {
//s.toCharArray();
s+=')';
cStack.push('('); //这两步就对应给字符串左右两边加一对括号
for(int i=0;i<s.length();i++){
char c=s.charAt(i);
if(c==' ') continue;
if(c=='(') cStack.push(c);
else if(c>='0'&&c<='9'){
int num=c-'0';
while(i+1<s.length() && s.charAt(i+1)>='0'&&s.charAt(i+1)<='9'){
i++;
num=num*10+s.charAt(i)-'0';
}
iStack.push(num);
}else if(c=='+'||c=='-'||c=='*'||c=='/'){
while(level(c)<=level(cStack.peek())){ //说明栈顶优先级高
popStackCompute();
}
cStack.push(c);
}else if(c==')'){
while(cStack.peek()!='('){ //遇到右括号,一直出栈计算,直到遇到左括号
popStackCompute();
}
cStack.pop();
}
}
return iStack.pop(); //最后还在栈中的就是答案了,因为每次运算之后都会push回去
}
}
题目链接:
剑指 Offer II 036. 后缀表达式
150. 逆波兰表达式求值
思路:表达式本来就是后缀的了,直接用一个操作数栈即可完成运算。
代码:
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack=new Stack<>();
for(String str:tokens){
if(str.equals("+")||str.equals("-")||str.equals("*")||str.equals("/")){
int num1=stack.pop();
int num2=stack.pop();
char c=str.charAt(0);
stack.push(calc(c,num2,num1));
}else{
stack.push(Integer.parseInt(str));
}
}
return stack.pop();
}
public int calc(char c,int num1,int num2){
switch(c){
case '+':
return num1+num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
return num1 / num2;
}
return 0;
}
}
扩展一种中缀转后缀的方法:括号法,这种方式比较容易记忆
中缀表达式:a + b * c + (d * e + f ) * g
1、按照运算符的优先级对所有的运算单位加括号,式子变成((a+(b*c))+(((d*e+f)*g))
2、转换前缀与后缀表达式
前缀:把运算符号移动到对应的括号前面:++(a*(bc))*(+(*(de)f)g)
,最后把括号去掉得到前缀表达式:++a*bc*+*defg
后缀:把运算符号移动到对应的括号后面:((a(bc)*)+(((de)*f)+g)*)+
,最后把括号去掉得到后缀表达式:abc*+de*f+g *+
参考:中缀表达式转后缀表达式