letcode 16.26. 计算器。
给定一个包含正整数、加(+)、减(-)、乘(*)、除(/)的算数表达式(括号除外),计算其结果。
表达式仅包含非负整数,+, - ,*,/ ,(,)四种运算符和空格 。 整数除法仅保留整数部分。
示例 1:
输入: "3+2*2"
输出: 7
示例 2:
输入: " 3/2 "
输出: 1
示例 3:
输入: " 3+5 / 2 "
输出: 5
说明:
你可以假设所给定的表达式都是有效的。
请不要使用内置的库函数 eval。
分析一个复杂的过程:
// 超级计算器:利用两个栈,一个数字栈,一个操作栈。过程利用逆序波兰表达式。
str="12*(2+(3+(4+2*(5+6))))$"; $为结束符号 看看如何做入栈出栈操作的。
用sb存上次的操作数字,这是为了解决大数字比如22 nowChar存当前字符 len为sb的length
详细分析:
i=0: 1 是数字 暂时存sb='1' nowChar='1'
i=1: 2 是数字 暂时存sb='12' nowChar='2'
i=2: * 不是数字 len>0 将sb入栈numStack[12.0], len=0,* 入栈operStack[*] nowChar='*'
i=3: ( 不是数字 len=0 sb不入栈,( 入栈operStack[*,(] nowChar='('
i=4: 2 是数字 暂时存sb='2' nowChar='2'
i=5: + 不是数字 len>0 将sb入栈numStack[12.0,2.0], len=0,+ 入栈operStack[*,(,+] nowChar='+'
i=6: ( 不是数字 len=0 sb不入栈,( 入栈operStack[*,(,+,(] nowChar='('
i=7: 3 是数字 暂时存sb='3' nowChar='3'
i=8: + 不是数字 len>0 将sb入栈numStack[12.0,2.0,3.0], len=0,+ 入栈operStack[*,(,+,(,+] nowChar='+'
i=9: ( 不是数字 len=0 sb不入栈,( 入栈operStack[*,(,+,(,+,(] nowChar='('
i=10: 4 是数字 暂时存sb='4' nowChar='4'
i=11: + 不是数字 len>0 将sb入栈numStack[12.0,2.0,3.0,4.0], len=0,+ 入栈operStack[*,(,+,(,+,(,+] nowChar='+'
i=12: 2 是数字 暂时存sb='2' nowChar='2'
i=13: * 不是数字 len>0 将sb入栈numStack[12.0,2.0,3.0,4.0,2.0], len=0,* 入栈operStack[*,(,+,(,+,(,+,*] nowChar='*'
i=14: ( 不是数字 len=0 sb不入栈,( 入栈operStack[*,(,+,(,+,(,+,*,(] nowChar='('
i=15: 5 是数字 暂时存sb='5' nowChar='5'
i=16: + 不是数字 len>0 将sb入栈numStack[12.0,2.0,3.0,4.0,2.0,5.0], len=0,+ 入栈operStack[*,(,+,(,+,(,+,*,(,+] nowChar='+'
i=17: 6 是数字 暂时存sb='6' nowChar='6'
i=16: ) 不是数字 len>0 将sb入栈numStack[12.0,2.0,3.0,4.0,2.0,5.0,6.0], len=0,
) 解栈标记 nowChar=')' 计算括号内的内容,直到找到成对'(':
首先:operStack出栈'+' 不是'('。那么根据数据栈弹出两个数据和当前操作符计算,5.0+6.0=11.0
并放入数据栈numStack[12.0,2.0,3.0,4.0,2.0,11.0],operStack[*,(,+,(,+,(,+,*,(].继续找
operStack出栈'(' 组成一对,'('弹出并break出当前循环operStack[*,(,+,(,+,(,+,*]
i=17: ) 不是数字 len=0 sb不入栈
) 解栈标记 nowChar=')' 计算括号内的内容,直到找到成对'(':
首先:operStack出栈'*' 不是'('。那么根据数据栈弹出两个数据和当前操作符计算,2.0+11.0=22.0
并放入数据栈numStack[12.0,2.0,3.0,4.0,22.0],operStack[*,(,+,(,+,(,+].继续找
operStack出栈'+' 不是'(' 根据数据栈弹出两个数据和当前操作符计算,4.0+22.0=26.0
入栈numStack[12.0,2.0,3.0,26.0] operStack[*,(,+,(,+,(]。继续。
出栈'('组成一对,'('弹出并break出当前循环operStack[*,(,+,(,+]
i=18到i=21 重复上面i7的过程得到numStack[12.0,31.0] operStack[*]
i=22 $ 为结束符号 len=0.说明上面入栈出栈完成。还需要查栈中是否操作完成以及len不为0的情况。栈不为空,继续计算
12.0*31.0=372.0 numStack[372.0] operStack[]
到此计算完成。
简单优先级计算分析:
// 超级计算器:利用两个栈,一个数字栈,一个操作栈。过程利用逆序波兰表达式。
str="3*4+1$"; $为结束符号 看看如何做入栈出栈操作的。
用sb存上次的操作数字,这是为了解决大数字比如22 nowChar存当前字符 len为sb的length
再来分析普通的情况:
i=0: numStack[] operStack[]
i=1: numStack[3] operStack[*]
i=2: numStack[3,4] operStack[*]
i=3: numStack[3,4,1] operStack[*,+]
i=4: $结束符号 numStack[3,4,1] operStack[*,+]
计算过程:(1+4)*3显然不对。* /的优先级应该大于+ -
所以要做下一次计算操作来前,operStack拿操作栈的栈顶符,是高优先级则计算。
i=3: + 是计算+/-符号 operStack[*]弹出是* /高优先级计算,则先计算3*4=12,入栈numStack[12].
再继续。
实际代码:
package com.jvm.stu;
import java.util.Stack;
import java.util.regex.Pattern;
public class Main {
static final Pattern EXGRE_NUM = Pattern.compile("^[0-9\\\\.]$");
public static void main(String[] args) {
// 为了方便计算我增加了一个$表示结束
System.out.println(calculate("3*4+2$"));
// System.out.println(calculate("2*(2+(3+(4+2*(5+6))))$"));
}
// 贴近计算改为返回double
public static double calculate(String s) {
Stack<Double> numStack= new Stack<>();
Stack<Character> operStack = new Stack<>();
// 用于处理123,0.123的情况
StringBuilder sb = new StringBuilder();
char nowChar = '$';
for (int i = 0; i < s.length(); ++i) {
char ch = s.charAt(i);
if (isNum(ch)) {
sb.append(ch);
} else if (isSymbol(ch)) {
if (sb.length() > 0) {
numStack.push(Double.valueOf(sb.toString()));
sb.setLength(0);
}
// + - 处理 比如情况 1+(-2)
if (isSign(ch) && ('(' == nowChar || '$' == nowChar || isOper(nowChar))) {
sb.append(ch);
} else if (')' == ch) {
while (!operStack.isEmpty()) {
if (operStack.peek() == '(') {
operStack.pop();
break;
}
calcu(numStack, operStack);
}
} else {
if (!operStack.isEmpty() && isSign(ch) && isHighOper(operStack.peek())) {
// 优先级高先计算
calcu(numStack, operStack);
}
operStack.push(ch);
}
}
nowChar = ch;
}
// 检查计算完全
if (sb.length() > 0) {
numStack.push(Double.valueOf(sb.toString()));
}
while (!operStack.isEmpty()) {
calcu(numStack, operStack);
}
return numStack.pop();
}
private static void calcu(Stack<Double> numStack, Stack<Character> operStack) {
Double num1 = Double.valueOf(numStack.pop());
// 当前弹出,为空栈
if (numStack.isEmpty()) {
numStack.push(num1);
}
Double num2 = Double.valueOf(numStack.pop());
switch (operStack.pop()) {
case '+': numStack.push(num2 + num1); break;
case '-': numStack.push(num2 - num1); break;
case '*': numStack.push(num2 * num1); break;
case '/': numStack.push(num2 / num1); break;
case '(': operStack.push('('); return;
default:
break;
}
}
// 数字
public static boolean isNum(char ch){
return EXGRE_NUM.matcher(ch + "").find();
}
// 优先级
public static boolean isHighOper(char ch){
return ch == '*' || ch == '/';
}
// 正负
public static boolean isSign(char ch){
return ch == '-' || ch == '+';
}
// 操作符
public static boolean isOper(char ch){
return isSign(ch) || isHighOper(ch);
}
public static boolean isSymbol(char ch){
return isOper(ch) || '(' == ch || ')' == ch;
}
}
这里存在优化的点:凡是能用stack实现的都能用linkedList实现,后者效率更高。不累述。只是将两个操作栈换了就行。实际算法中能不用Stack就不用,最好都用linkedList。
计算:34+2$
Stack下运行时间:
linkedList下运行时间:
复杂计算:2(2+(3+(4+2*(5+6))))$
Stack下运行时间:
linkedList下运行时间:
从上面可以看到,简单计算Stack快,复杂计算linkedList。优化从来不是一概而论,具体场景具体优化。