基本计算器
1.1 问题描述
实现一个基本的计算器来计算一个简单的字符串表达式 s 的值。
示例 1:
输入:s = “1 + 1”
输出:2示例 2:
输入:s = " 2-1 + 2 "
输出:3
1.2 思路及复杂度分析
因为睡眠不好的原因,总觉得头胀胀的,精神状态不好,但每日算法的断更又是我所不能接受的。所以这一篇我就用了力扣官方的题解,来为大家解析一下思路并做适当延伸。
题目要求满足+/-两种操作符
的运算,且操作数都为非负整数
,而且包含括号
。
首先明确的是,对于这种表达式求值的问题,编译器对于表达式求值都是通过栈来实现的,我们这里也不例外。
另外小学的知识告诉我们有括号的话,应该先算括号里面的表达式,再将子表达式求解的结果作为表达式的一部分继续运算。此外,还有一个容易忽略的地方,那就是空格
!!!很多人出于美观的编程习惯,会在表达式中加入空格,这样对表达式的求解并没有影响,但我们在编程时需要刻意的忽略空格。最后,需要注意的还有对操作数的处理,在一个字符串中123被认定为三个字符!我们需要使用十进制的运算知识将三个字符组合在一起处理。
到此,基本计算器的核心部分就完结了,具体的代码看最后的代码演示部分就ok了。
- 注:在力扣题解区发现大牛的奇思妙想:https://leetcode-cn.com/problems/basic-calculator/solution/zhan-di-gui-20xing-dai-ma-chao-96-by-mantoufan/
您可能会说,我点进来你就给我看这个,不是说好有干货吗~
的确,在我看来呢,掌握上面的的确可以解决问题,但它不能作为一种解决问题的通用模式,依靠这种灵光一现的技巧是不长远的,对于表达式求值的问题,更通用的解法是逆波兰表达式
——
刚好力扣上有这么一道题,我们来一起小试牛刀吧~
逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的运算符包括
+
,-
,*
,/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
思路及复杂度分析
在分析之前,有必要先聊聊逆波兰表达式是什么,为什么它对于表达式的求解有帮助?
逆波兰表达式是一种利用栈来进行运算的数学表达式。我们通常使用的表达式是中缀表达式,比如:
( 1 + 2 ) ∗ ( 3 − 4 ) (1+2)*(3-4) (1+2)∗(3−4)
计算机求解时会将这个中缀表达式先解析为符号树,再求值。而解析符号树需要递归遍历这颗树,假如表达式比较复杂,也就是树的深度大时,会申请大量栈空间,性能不高,而如果将中缀表达式变成后缀表达式时,情况就有所不同了,只需要很小的栈空间就可以完成操作。
说到这,你可能还是似懂非懂,同样的表达式内容,为什么逆波兰表达式效率就高呢~
这是因为逆波兰表达式可以简化表达式的内容,它设计的初衷就是因为可以省略括号,从而减少CPU运算的次数。
既然逆波兰表达式这么好用,那怎么将中缀表达式转化为逆波兰表达式呢?那就要看调度车算法咯——
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于找顶符号(乘除优先加减)则栈顶元素依次出找并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
对于其具体的案例,可以移步这篇博文:http://www.nowamagic.net/librarys/veda/detail/2307
看到这的小伙伴可以舒一口气了,内功已成,剩下的招式就是信手拈来啦
。但是追求更好的性能是一个coder应该有的习惯,所以我们会从栈和数组模拟栈两种思路来求解——
栈求解
它的思路可谓非常朴素,我们先遍历数组,将所有的操作数入栈,当遇到运算符时,就取出栈顶的两个元素,并将运算的结果压栈,
将遍历得到的字符串转化为整数时,有一个小细节值得注意:是使用Integer.parseInt()还是Integer.ValueOf(),它们都可以将字符串转化为整数,但前者比起后者少了装箱的操作.
数组模拟栈求解
虽然栈提供了对出栈入栈操作提供了方便的封装,但也导致其操作不灵活,就拿这句代码来看——
完全可以用更简洁的数组代码实现:
此外,只需要申请一个数组用于保存操作数即可,而假设一个表达式长度为n,则最怀情况下,这个表达式中只包含操作数,并且操作数只有一个,也就是
n / 2 + 1 n/2+1 n/2+