Java 整数计算器(支持加减乘 + 括号)学习笔记
一、核心需求
实现一个支持 加减乘 三种运算和 括号 的整数计算器,要求时间复杂度 (O(n))、空间复杂度 (O(n)),计算结果在整型范围内。
二、核心算法:双栈法
1. 算法原理
通过 操作数栈(存储数字)和 运算符栈(存储+、-、*、(),结合 运算符优先级 控制运算顺序,核心规则:
- 数字:拼接多位数后压入操作数栈。
- 左括号(:直接压入运算符栈,等待右括号匹配。
- 右括号):循环计算栈内运算符,直到遇到左括号(弹出左括号,不参与计算)。
- 运算符+、-、*:先计算栈内 优先级 ≥ 当前运算符 的所有操作,再将当前运算符入栈。
- 遍历结束后:计算栈内剩余所有运算符,最终操作数栈顶即为结果。
2. 优先级定义
| 运算符 | 优先级 | 说明 |
|---|---|---|
| ( | 0 | 优先级最低,遇到右括号才计算 |
| +、- | 1 | 同优先级,从左到右计算 |
| * | 2 | 优先级最高,优先计算 |
三、关键 API 与语法解析
1. 字符判断:Character.isDigit(c)
- 作用:判断字符 c 是否为 0-9 的数字字符。
- 替代写法:c >= '0' && c <= '9'(效果完全一致,API 更简洁)。
- 使用场景:识别表达式中的数字字符,实现多位数拼接。
2. 双端队列实现栈:Deque
- 作用:替代 Java 遗留类 Stack,作为栈的实现(官方推荐,更高效安全)。
- 核心方法:
-
- push(E e):元素压入栈顶。
-
- pop():弹出栈顶元素并返回。
-
- peek():查看栈顶元素(不弹出)。
- 示例:
Deque<Integer> numStack = new ArrayDeque<>(); // 操作数栈
Deque<Character> opStack = new ArrayDeque<>(); // 运算符栈
3. 多位数拼接逻辑
if (Character.isDigit(c)) {
int num = 0;
// 循环拼接连续数字,i < n 防止下标越界
while (i < n && Character.isDigit(s.charAt(i))) {
num = num * 10 + (s.charAt(i) - '0'); // 高位到低位拼接
i++;
}
numStack.push(num);
i--; // 回退下标,避免跳过下一个字符
}
- 关键:i < n 必须放在前面,利用 && 短路特性,防止 s.charAt(i) 抛出 索引越界异常。
4. 自定义计算方法:calculate()
封装 弹出 - 计算 - 压栈 逻辑,避免代码冗余:
private void calculate(Deque<Integer> numStack, Deque<Character> opStack) {
if (numStack.size() < 2 || opStack.isEmpty()) return;
int num2 = numStack.pop(); // 先弹出的是第二个操作数
int num1 = numStack.pop(); // 后弹出的是第一个操作数
char op = opStack.pop();
int res = 0;
switch (op) {
case '+' : res = num1 + num2; break;
case '-' : res = num1 - num2; break;
case '*' : res = num1 * num2; break;
}
numStack.push(res); // 结果压回操作数栈
}
- 注意:操作数弹出顺序 → 先弹 num2,后弹 num1,否则运算顺序错误(如减法 num1 - num2)。
四、常见错误与 Debug 技巧
1. 致命错误:循环条件写反
// 错误写法:栈空时执行,永远不会触发计算
while (opStack.isEmpty()) { calculate(...); }
// 正确写法:栈不为空时,处理剩余运算符
while (!opStack.isEmpty()) { calculate(...); }
- 影响:不修复会导致遍历结束后,栈内剩余运算符未计算,结果完全错误。
2. 语法错误:return 后不可加 break
// 错误写法:return 后 break 是不可达代码,编译报错
case '*' : return 2; break;
// 正确写法:return 直接结束方法,无需 break
case '*' : return 2;
3. 边界错误:空表达式处理
输入空字符串时,直接 numStack.pop() 会抛出 空栈异常,需提前判断:
if (s.length() == 0) return 0;
4. 方法定义错误:返回值类型不匹配
// 错误写法:void 方法不能返回值
private void getPriority(char op) { return 2; }
// 正确写法:返回优先级(int 类型)
private int getPriority(char op) {
switch (op) {
case '*' : return 2;
case '+' : case '-' : return 1;
case '(' : return 0;
default : return -1;
}
}
六、核心知识点总结
- 双栈法 是处理带优先级 / 括号表达式的经典算法,核心是 优先级控制运算顺序。
- 索引越界预防:遍历字符串时,务必先判断 i < n,再访问 s.charAt(i)。
- 方法封装:重复逻辑(如计算)抽成独立方法,提升代码可读性和复用性。
- 边界条件处理:空输入、栈空判断是代码健壮性的关键。
- Java 语法细节:return 后不可加 break,方法返回值类型必须与 return 匹配。