牛客网新手入门130_88-89题_20260114

3 阅读3分钟

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

// 错误写法:returnbreak 是不可达代码,编译报错
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;
    }
}

六、核心知识点总结

  1. 双栈法 是处理带优先级 / 括号表达式的经典算法,核心是 优先级控制运算顺序
  1. 索引越界预防:遍历字符串时,务必先判断 i < n,再访问 s.charAt(i)。
  1. 方法封装:重复逻辑(如计算)抽成独立方法,提升代码可读性和复用性。
  1. 边界条件处理:空输入、栈空判断是代码健壮性的关键。
  1. Java 语法细节:return 后不可加 break,方法返回值类型必须与 return 匹配。