四则运算表达式求值
问题背景
我们需要实现一个计算器,它能够解析一个以字符串形式表示的、仅包含整数和基本四则运算符的数学表达式,并计算出其结果。
计算规则
-
运算符: 表达式中只包含加
+、减-、乘*、除/四种运算符。 -
运算优先级:
- 乘
*和 除/的计算优先级高于 加+和 减-。 - 相同优先级的运算符按照从左到右的顺序计算。
- 乘
-
整数运算: 所有运算均为整数运算。对于除法,结果需要向零取整(例如,
5/2=2,-5/2=-2)。 -
错误处理: 在计算过程中,如果出现除以零的情况,应立即停止计算,并将结果标记为错误。
任务要求
给定一个字符串形式的计算表达式 expression,请根据上述规则对其求值。
- 如果计算成功,以字符串形式返回最终的计算结果。
- 如果计算过程中遇到除以零的情况,则返回字符串
"error"。
输入格式
-
expression: 一个字符串,代表待计算的数学表达式。1 <= expression.length <= 100
输出格式
- 一个表示十进制整数的字符串;或者,在发生除零错误时,输出固定的错误字符串
"error"。
限制与要求
-
时间限制: C/C++
1000ms, 其他语言2000ms -
内存限制: C/C++
64MB, 其他语言128MB -
用例保证:
- 输入的数字、计算过程中的中间值以及最终结果,都在32位有符号整数(
int)的范围内。 - 输入表达式的格式合法。
- 输入的数字、计算过程中的中间值以及最终结果,都在32位有符号整数(
样例说明
样例 1
-
输入:
"1+2*3-100/2" -
输出:
"-43" -
解释:
-
根据运算符优先级,首先计算乘法和除法。
2 * 3 = 6100 / 2 = 50
-
原表达式等价于计算
"1 + 6 - 50"。 -
从左到右依次计算加法和减法。
1 + 6 = 77 - 50 = -43
-
最终结果转换为字符串为
"-43"。
-
样例 2
-
输入:
"3/0" -
输出:
"error" -
解释:
- 表达式中包含
3 / 0的计算。 - 在执行除法时,检测到除数为零。
- 计算立即停止,并按要求返回错误字符串
"error"。
- 表达式中包含
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Scanner;
/**
* LeetCode 227 / 类似问题: 基本计算器 II
* 解决包含 +、-、*、/ 四则运算的字符串表达式求值问题。
*
* 核心思想:双栈法
* 使用一个栈存储操作数(数字),另一个栈存储操作符。
* 遍历表达式字符串,根据操作符的优先级来决定是立即计算还是先将操作符入栈。
*
* 算法步骤:
* 1. 初始化一个操作数栈 (numStack) 和一个操作符栈 (opStack)。
* 2. 从左到右遍历表达式字符串中的每个字符。
* a. 如果是数字,则解析出完整的整数,并压入 numStack。
* b. 如果是操作符,则与 opStack 栈顶的操作符进行优先级比较:
* - 只要 opStack 不为空,且栈顶操作符的优先级 **大于或等于** 当前操作符的优先级,
* 就从 opStack 弹出一个操作符,从 numStack 弹出两个操作数进行计算,然后将结果压回 numStack。
* - 循环比较结束后,将当前操作符压入 opStack。
* 3. 遍历完整个字符串后,opStack 中可能还有剩余的操作符。
* 依次弹出操作符和操作数进行计算,直到 opStack 为空。
* 4. 最终,numStack 中剩下的唯一一个数字就是整个表达式的结果。
* 5. 在计算过程中,特别处理除法操作,如果除数为 0,则立即停止并返回 "error"。
*/
public class Solution {
/**
* 对给定的字符串形式的四则运算表达式进行求值。
*
* @param s 包含数字和 '+', '-', '*', '/' 的字符串表达式。
* @return 表达式的计算结果,以字符串形式返回。如果计算过程中出现除零,则返回 "error"。
*/
public String calculate(String s) {
// Deque (双端队列) 是 Java 中推荐用来实现栈的数据结构
// numStack: 存储操作数
Deque<Integer> numStack = new ArrayDeque<>();
// opStack: 存储操作符
Deque<Character> opStack = new ArrayDeque<>();
try {
// 使用 for 循环遍历整个表达式字符串
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// 跳过空格
if (c == ' ') {
continue;
}
// 如果当前字符是数字
if (Character.isDigit(c)) {
// 解析一个完整的数字(可能不止一位)
int num = 0;
while (i < s.length() && Character.isDigit(s.charAt(i))) {
// (s.charAt(i) - '0') 将字符数字转换为整数值
num = num * 10 + (s.charAt(i) - '0');
i++;
}
// 将解析出的数字压入操作数栈
numStack.push(num);
// 因为 for 循环的 i++ 会额外执行一次,而 while 循环已经将 i 移动到数字末尾的下一个位置,
// 所以需要 i-- 来抵消 for 循环多余的自增,确保下一次循环从正确的位置开始。
i--;
}
// 如果当前字符是操作符
else {
// 规则:只要操作符栈不为空,且栈顶操作符的优先级 >= 当前操作符的优先级
// 就要先计算栈顶的操作(保证了从左到右的计算顺序和乘除优先)
while (!opStack.isEmpty() && precedence(opStack.peek()) >= precedence(c)) {
calculateTop(numStack, opStack);
}
// 将当前操作符压入操作符栈
opStack.push(c);
}
}
// --- 遍历完整个表达式字符串后 ---
// 将栈中剩余的所有操作符依次计算
while (!opStack.isEmpty()) {
calculateTop(numStack, opStack);
}
// 最终,数字栈中剩下的唯一一个数就是结果
// 处理输入为空或格式错误导致栈为空的情况
if (numStack.isEmpty()) {
return "0"; // 或者根据题意返回错误
}
return String.valueOf(numStack.pop());
} catch (ArithmeticException e) {
// 如果在计算过程中捕获到我们主动抛出的除零异常,返回 "error"
return "error";
}
}
/**
* 从栈顶取出一个操作符和两个操作数进行计算,并将结果压回操作数栈。
*
* @param numStack 操作数栈
* @param opStack 操作符栈
* @throws ArithmeticException 如果发生除零操作
*/
private void calculateTop(Deque<Integer> numStack, Deque<Character> opStack) {
// 检查操作数是否足够
if (numStack.size() < 2) {
throw new ArithmeticException("Invalid Expression");
}
// 弹出两个操作数和一个操作符
// 注意顺序:栈是后进先出,所以先弹出的是右操作数
int num2 = numStack.pop();
int num1 = numStack.pop();
char op = opStack.pop();
// 执行计算
int result;
switch (op) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
// 检查除零错误
if (num2 == 0) {
throw new ArithmeticException("Division by zero");
}
result = num1 / num2;
break;
default:
// 不应发生,因为输入字符受限
throw new IllegalArgumentException("Invalid operator: " + op);
}
// 将计算结果压回操作数栈
numStack.push(result);
}
/**
* 定义运算符的优先级。
*
* @param op 操作符
* @return 优先级数值(数值越大,优先级越高)
*/
private int precedence(char op) {
if (op == '+' || op == '-') {
return 1; // 加减优先级低
}
if (op == '*' || op == '/') {
return 2; // 乘除优先级高
}
// 对于其他字符(例如括号,虽然本题没有),可以返回 0
return 0;
}
}
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String expression = scanner.nextLine(); // 读取一行表达式
scanner.close();
// 创建 Solution 类的实例并调用方法
Solution solution = new Solution();
System.out.println(solution.calculate(expression));
}
}