简单四则运算解析器之题解
题目概述
小F面临一个编程挑战:实现一个基本的计算器来计算简单的字符串表达式的值。该字符串表达式有效,并可能包含数字(0-9)、运算符+、-及括号()。
限制是
- 字符串中不包含空格。除法运算应只保留整数结果。实现一个解析器计算这些表达式的值,且不使用任何内置的
eval函数。测试样例
- 样例1: 输入:
expression = "1+1"
输出:2- 样例2: 输入:
expression = "3+4*5/(3+2)"
输出:7
解题思路
数据结构的选择
- 栈:栈是一种非常适合处理表达式计算的数据结构,尤其是按照题意需要处理括号和运算符优先级时。本题可以使用两个栈:一个用于存储操作数(数字),另一个用于存储运算符。
算法实现步骤
- 初始化栈:创建两个栈,一个用于存储操作数(数字),另一个用于存储运算符。
- 遍历表达式:从左到右遍历表达式中的每一个字符。
- 如果遇到数字,将其压入操作数栈。
- 如果遇到运算符,将其与运算符栈顶的运算符进行比较,如果当前运算符优先级低于或等于栈顶运算符,则弹出栈顶运算符并进行计算,直到满足条件为止,然后将当前运算符压入栈。
- 如果遇到左括号
(,直接将其压入运算符栈。 - 如果遇到右括号
),则弹出运算符栈中的运算符并进行计算,直到遇到左括号(为止。
- 计算结果:遍历结束后,如果运算符栈中还有运算符,则依次弹出并进行计算,直到运算符栈为空。
-
- 返回结果:最终操作数栈中的唯一元素即为表达式的计算结果。
实现代码
import java.util.Stack;
public class Main {
public static int solution(String expression) {
Stack<Integer> operands = new Stack<>();
Stack<Character> operators = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char ch = expression.charAt(i);
if (Character.isDigit(ch)) {
// 处理数字
operands.push(ch - '0');
} else if (ch == '(') {
// 处理左括号
operators.push(ch);
} else if (ch == ')') {
// 处理右括号
while (operators.peek() != '(') {
// 计算括号内的表达式
calculate(operands, operators);
}
operators.pop(); // 弹出左括号
} else if (ch == '+' || ch == '-' || ch == '*' || ch == '/') {
// 处理运算符
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(ch)) {
// 计算优先级高的运算符
calculate(operands, operators);
}
operators.push(ch);
}
}
// 处理剩余的运算符
while (!operators.isEmpty()) {
calculate(operands, operators);
}
return operands.pop();
}
private static void calculate(Stack<Integer> operands, Stack<Character> operators) {
char op = operators.pop();
int b = operands.pop();
int a = operands.pop();
int result = 0;
switch (op) {
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
result = a / b; // 整数除法
break;
}
operands.push(result);
}
private static int precedence(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
public static void main(String[] args) {
System.out.println(solution("1+1") == 2);
System.out.println(solution("3+4*5/(3+2)") == 7);
System.out.println(solution("4+2*5-2/1") == 12);
System.out.println(solution("(1+(4+5+2)-3)+(6+8)") == 23);
}
}
输出范例
true
true
true
true
具体运行过程
-
初始化栈:
- 创建两个栈:
operands用于存储操作数(数字),operators用于存储运算符。
- 创建两个栈:
-
遍历表达式:
- 使用
for循环遍历表达式中的每一个字符。
- 使用
-
处理数字:
- 如果当前字符是数字(
Character.isDigit(ch)返回true),将其转换为整数并压入operands栈。 - 例如,对于表达式
"1+1",当遍历到'1'时,operands栈会压入1。
- 如果当前字符是数字(
-
处理左括号
(:- 如果当前字符是左括号
'(',直接将其压入operators栈。 - 例如,对于表达式
"(1+2)",当遍历到'('1. - 时,operators栈会压入'('。
- 如果当前字符是左括号
-
处理右括号
):- 如果当前字符是右括号
')',则不断从operators栈中弹出运算符并进行计算,直到遇到左括号'('为止。最后弹出左括号。 - 例如,对于表达式
"(1+2)",当遍历到')'时,会弹出'+'并计算1 + 2,结果3压入operands栈,最后弹出'('。
- 如果当前字符是右括号
-
处理运算符:
-
如果当前字符是运算符(
'+','-','*','/'),则将其与operators栈顶的运算符进行比较:- 如果栈顶运算符的优先级大于或等于当前运算符,则弹出栈顶运算符并进行计算,直到栈顶运算符的优先级小于当前运算符。
- 将当前运算符压入
operators栈。
-
例如,对于表达式
"3+4*5",当遍历到'*'时,会先计算4 * 5,结果20压入operands栈,然后将'+'压入operators栈。
-
计算结果:
- 遍历结束后,如果
operators栈中还有运算符,则依次弹出并进行计算,直到operators栈为空。 - 例如,对于表达式
"3+4*5",遍历结束后,operators栈中还有'+',会计算3 + 20,结果23压入operands栈。
- 遍历结束后,如果
-
返回结果:
- 最终
operands栈中的唯一元素即为表达式的计算结果。 - 例如,对于表达式
"3+4*5",最终operands栈中只有一个元素23,返回23。
- 最终
关键方法
-
calculate方法:- 从
operands栈中弹出两个操作数a和b,从operators栈中弹出一个运算符op,根据运算符进行计算,并将结果压回operands栈。
- 从
-
precedence方法:- 返回运算符的优先级,用于比较运算符的优先级。
需要注意的tips:
- Java 中的 Stack 类:Java 提供了
java.util.Stack类,用于实现栈的功能
Stack<Integer> operands = new Stack<>();
Stack<Character> operators = new Stack<>();
- 字符串遍历:使用
for循环遍历字符串中的每个字符。 - 字符判断:使用
Character.isDigit(ch)判断字符是否为数字。
for (int i = 0; i < expression.length(); i++) {
char ch = expression.charAt(i);
if (Character.isDigit(ch)) {
// 处理数字
}
}
- 边界条件处理
- 栈为空:在弹出栈元素之前,先检查栈是否为空,以避免
EmptyStackException。 - 多位数处理:当前代码假设所有数字都是个位数,需要处理多位数的情况。
- 异常处理
- 非法表达式:在处理右括号时,如果栈中没有左括号,则表达式非法,可以抛出异常。