【LeetCode】表达式求值、后缀表达式、计算器系列

题目链接:
面试题 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 *+

参考:中缀表达式转后缀表达式

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
算法思路: 1. 创建两个栈:一个操作数栈和一个操作符栈。 2. 从左到右遍历表达式,遇到操作数就压入操作数栈,遇到操作符就与操作符栈中的栈顶元素比较优先级。 3. 如果当前操作符的优先级大于操作符栈顶的操作符优先级,就将当前操作符压入操作符栈。 4. 如果当前操作符的优先级小于或等于操作符栈顶的操作符优先级,就从操作数栈中弹出两个操作数,从操作符栈中弹出一个操作符,进行计算并将结果压入操作数栈,重复步骤3,直到当前操作符的优先级大于操作符栈顶的操作符优先级。 5. 当遍历完表达式后,如果操作符栈中还有操作符,就将操作数栈中剩余的操作数和操作符栈中的操作符按照步骤4计算,直到操作符栈为空。 6. 操作数栈中最后剩下的元素就是表达式的值。 算法实现: ```python def calculate(s: str) -> int: # 定义操作符优先级 priority = {'+': 1, '-': 1, '*': 2, '/': 2} # 定义操作数栈和操作符栈 nums = [] ops = [] i = 0 while i < len(s): # 跳过空格 if s[i] == ' ': i += 1 continue # 如果是数字,压入操作数栈 if s[i].isdigit(): j = i while j < len(s) and s[j].isdigit(): j += 1 nums.append(int(s[i:j])) i = j # 如果是操作符,比较优先级 else: while ops and priority[ops[-1]] >= priority[s[i]]: b = nums.pop() a = nums.pop() op = ops.pop() if op == '+': nums.append(a + b) elif op == '-': nums.append(a - b) elif op == '*': nums.append(a * b) elif op == '/': nums.append(int(a / b)) ops.append(s[i]) i += 1 # 处理剩余的操作符 while ops: b = nums.pop() a = nums.pop() op = ops.pop() if op == '+': nums.append(a + b) elif op == '-': nums.append(a - b) elif op == '*': nums.append(a * b) elif op == '/': nums.append(int(a / b)) # 返回操作数栈中最后剩下的元素 return nums[-1] ``` 算法分析: 1. 时间复杂度:遍历一遍表达式,时间复杂度为O(n);操作符栈和操作数栈中每个元素都只进出一次,所以总时间复杂度为O(n)。 2. 空间复杂度:操作符栈和操作数栈的最大长度为表达式的长度,所以空间复杂度为O(n)。 参考链接: 1. [LeetCode官方题解](https://leetcode-cn.com/problems/basic-calculator-ii/solution/jian-dan-zhan-jie-fa-by-jerry_nju/) 2. [算法珠玑(第2版)](https://book.douban.com/subject/33437322/)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

童话ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值