1.1 问题描述
实现一个基本的计算器来计算一个简单的字符串表达式 s 的值。
示例 1:
输入:s = "1 + 1" 输出:2
示例 2:
输入:s = " 2-1 + 2 " 输出:3
1.2 思路及复杂度分析
因为睡眠不好的原因,总觉得头胀胀的,精神状态不好,但每日算法的断更又是我所不能接受的。所以这一篇我就用了力扣官方的题解,来为大家解析一下思路并做适当延伸。
题目要求满足+/-两种操作符的运算,且操作数都为非负整数,而且包含括号。
首先明确的是,对于这种表达式求值的问题,编译器对于表达式求值都是通过栈来实现的,我们这里也不例外。
另外小学的知识告诉我们有括号的话,应该先算括号里面的表达式,再将子表达式求解的结果作为表达式的一部分继续运算。此外,还有一个容易忽略的地方,那就是空格!!!很多人出于美观的编程习惯,会在表达式中加入空格,这样对表达式的求解并没有影响,但我们在编程时需要刻意的忽略空格。最后,需要注意的还有对操作数的处理,在一个字符串中123被认定为三个字符!我们需要使用十进制的运算知识将三个字符组合在一起处理。
到此,基本计算器的核心部分就完结了,具体的代码看最后的代码演示部分就ok了。
- 注:在力扣题解区发现大牛的奇思妙想:leetcode-cn.com/problems/ba…
您可能会说,我点进来你就给我看这个,不是说好有干货吗~
的确,在我看来呢,掌握上面的的确可以解决问题,但它不能作为一种解决问题的通用模式,依靠这种灵光一现的技巧是不长远的,对于表达式求值的问题,更通用的解法是逆波兰表达式——
刚好力扣上有这么一道题,我们来一起小试牛刀吧~
逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的运算符包括
+,-,*,/。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
思路及复杂度分析
在分析之前,有必要先聊聊逆波兰表达式是什么,为什么它对于表达式的求解有帮助?
逆波兰表达式是一种利用栈来进行运算的数学表达式。我们通常使用的表达式是中缀表达式,比如:
计算机求解时会将这个中缀表达式先解析为符号树,再求值。而解析符号树需要递归遍历这颗树,假如表达式比较复杂,也就是树的深度大时,会申请大量栈空间,性能不高,而如果将中缀表达式变成后缀表达式时,情况就有所不同了,只需要很小的栈空间就可以完成操作。
说到这,你可能还是似懂非懂,同样的表达式内容,为什么逆波兰表达式效率就高呢~
这是因为逆波兰表达式可以简化表达式的内容,它设计的初衷就是因为可以省略括号,从而减少CPU运算的次数。
既然逆波兰表达式这么好用,那怎么将中缀表达式转化为逆波兰表达式呢?那就要看调度车算法咯——
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于找顶符号(乘除优先加减)则栈顶元素依次出找并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
对于其具体的案例,可以移步这篇博文:www.nowamagic.net/librarys/ve…
看到这的小伙伴可以舒一口气了,内功已成,剩下的招式就是信手拈来啦。但是追求更好的性能是一个coder应该有的习惯,所以我们会从栈和数组模拟栈两种思路来求解——
栈求解
它的思路可谓非常朴素,我们先遍历数组,将所有的操作数入栈,当遇到运算符时,就取出栈顶的两个元素,并将运算的结果压栈,
将遍历得到的字符串转化为整数时,有一个小细节值得注意:是使用Integer.parseInt()还是Integer.ValueOf(),它们都可以将字符串转化为整数,但前者比起后者少了装箱的操作.
数组模拟栈求解
虽然栈提供了对出栈入栈操作提供了方便的封装,但也导致其操作不灵活,就拿这句代码来看——
完全可以用更简洁的数组代码实现:
此外,只需要申请一个数组用于保存操作数即可,而假设一个表达式长度为n,则最怀情况下,这个表达式中只包含操作数,并且操作数只有一个,也就是
趣味图解
图片来自力扣大佬的题解:leetcode-cn.com/problems/ev…
代码演示
package com.zyz.Stack;
/**
* 逆波兰表达式
*
*/
public class Solution2 {
public int evalRPN(String[] tokesns) {
//申请一个存储操作数的数组
int[] res = new int[tokesns.length/2+1];
int index = 0;
//遍历元素,如果是数字,则放到数组中
for (String str:tokesns) {
switch (str){
case "+":
res[index-2] += res[--index];
break;
case "-":
res[index-2] -= res[--index];
break;
case "*":
res[index-2] *= res[--index];
break;
case "/":
res[index-2] /= res[--index];
break;
default:
res[index++] = Integer.parseInt(str);
Integer.valueOf(str)
break;
}
}
return res[0];
}
}
class Solution {
public int evalRPN(String[] tokesns) {
Stack<Integer> stack = new Stack<>();
int op1 = 0;
int op2 = 0;
for(String str:tokesns){
switch (str){
case "+":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op1+op2);
break;
case "-":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op2-op1);
break;
case "*":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op1*op2);
break;
case "/":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op2/op1);
break;
default:
stack.push(Integer.parseInt(str));
break;
}
}
return stack.pop();
}
}
1.3 代码演示
class Solution {
public int calculate(String s) {
//为了避免运算时数据类型无法转换,选用Object
Stack<Object> stack = new Stack<>();
int operand = 0;
int n = 0;
//逆序字符串
for(int i=s.length()-1;i>=0;--i){
//对入栈的字符进行判断
char ch = s.charAt(i);
//数字的话,则组合
if(Character.isDigit(ch)){
operand += (int)Math.pow(10,n)*(int)(ch-'0');
++n;
}else if(ch != ' '){//不是空格,也不是数字,则应该是操作符和操作数
//前置处理,逻辑追加:清0操作数,清零计数
if(n != 0){
stack.push(operand);
n = 0;
operand = 0;
}
//左括号
if(ch == '('){//左括号的话,运算栈中子表达式的结果直到碰到右括号,弹栈,压栈
int res = calculateExp(stack);
stack.pop();
stack.push(res);
}else{//右括号,或者+-操作符
stack.push(ch);
}
}
}
if(n != 0)
stack.push(operand);
return calculateExp(stack);
}
private static int calculateExp(Stack<Object> stack) {
int res = 0;
//特判,非空
if(!stack.empty())
res = (int)stack.pop();
while(!stack.empty() && (char)stack.peek()!=')'){
//+号运算符
char ch = (char)stack.pop();
if('+' == ch){
res += (int)stack.pop();
}else{
res -= (int)stack.pop();
}
}
return res;
}
}
日拱一卒,功不唐捐。