学习方法与心得-中52-简单四则运算解析器-Java实现 | 豆包MarsCode AI刷题

61 阅读3分钟

简单计算器

问题描述

我们的目标是实现一个计算器,接受一个有效的数学表达式字符串,并返回其计算结果。表达式的格式是:

  • 只包含数字(0-9)、运算符(+、-、*、/)和括号(())。
  • 运算符的优先级符合常见规则:*/ 的优先级高于 +-,并且括号内的运算具有更高的优先级。
  • 除法运算应返回整数结果,向下取整。

解题思路

为了解析这个表达式并计算其值,我们需要采取以下步骤:

  1. 转换中缀表达式为后缀表达式(也叫逆波兰表示法,RPN):这样可以简化运算的顺序,避免了括号的复杂性。
  2. 计算后缀表达式:使用栈来处理后缀表达式,按运算符的优先级计算并返回结果。

步骤一:转换中缀表达式为后缀表达式

中缀表达式是我们常见的数学表达式,比如 3 + 4 * 5 / (3 + 2)。计算这样的表达式时,通常我们需要使用优先级规则和括号来确定运算的顺序。为了解决这个问题,我们可以使用 来处理运算符,并使用另一个栈来保存数字。

关键的操作:

  • 当遇到数字时,直接将其添加到结果中。
  • 当遇到运算符时,根据当前运算符和栈中运算符的优先级来决定是否将当前运算符加入栈中,或者将栈中的运算符弹出并加入到结果中。
  • 当遇到右括号 ) 时,弹出运算符直到遇到左括号 (

步骤二:计算后缀表达式

一旦得到了后缀表达式,我们可以用一个栈来计算结果。遇到数字时,直接压栈;遇到运算符时,弹出栈顶的两个数字,进行计算并将结果重新压入栈中。最终栈中的唯一元素即为计算结果。

优化与实现

在实现时,我们将对以下几个方面进行优化:

  1. 运算符优先级的维护:使用哈希映射来存储每个运算符的优先级。
  2. 栈的使用:通过栈来管理操作符和操作数。
  3. 反向计算:在完成中缀到后缀的转换后,再通过栈来计算最终结果。

Java 代码实现

import java.util.HashMap;
import java.util.Stack;

public class Calculator {
    public static int solution(String expression) {
        HashMap<Character, Integer> priority = new HashMap<>();
        priority.put('(', -1);
        priority.put('+', 1);
        priority.put('-', 1);
        priority.put('*', 2);
        priority.put('/', 2);

        Stack<Character> operators = new Stack<>();
        Stack<Character> reverseResult = new Stack<>();
        char[] exp = expression.toCharArray();

        // Step 1: Convert the expression to Reverse Polish Notation (RPN)
        for (char c : exp) {
            if (Character.isDigit(c)) {
                reverseResult.push(c);
                continue;
            }

            if (operators.empty()) {
                operators.push(c);
            } else {
                if (c == ')') {
                    while (operators.peek() != '(') {
                        reverseResult.push(operators.pop());
                    }
                    operators.pop();
                    continue;
                }

                if (priority.get(c) > priority.get(operators.peek()) || c == '(') {
                    operators.push(c);
                    continue;
                }

                while (!operators.empty() && priority.get(c) <= priority.get(operators.peek())) {
                    reverseResult.push(operators.pop());
                }
                operators.push(c);
            }
        }

        // Push remaining operators to the result stack
        while (!operators.empty()) {
            reverseResult.push(operators.pop());
        }

        // Step 2: Evaluate the Reverse Polish Notation (RPN)
        Stack<Integer> sum = new Stack<>();
        while (!reverseResult.isEmpty()) {
            char c = reverseResult.pop();
            if (Character.isDigit(c)) {
                sum.push(Character.getNumericValue(c));
            } else {
                int b = sum.pop();
                int a = sum.pop();
                switch (c) {
                    case '+': sum.push(a + b); break;
                    case '-': sum.push(a - b); break;
                    case '*': sum.push(a * b); break;
                    case '/': sum.push(a / b); break;
                }
            }
        }

        return sum.pop();
    }

    public static void main(String[] args) {
        System.out.println(solution("1+1"));                    // Output: 2
        System.out.println(solution("3+4*5/(3+2)"));            // Output: 7
        System.out.println(solution("4+2*5-2/1"));              // Output: 12
        System.out.println(solution("(1+(4+5+2)-3)+(6+8)"));    // Output: 23
        System.out.println(solution("2*(5+5*2)/3+(6+8*3)"));    // Output: 40
    }
}

代码解释

  1. 优先级表:使用一个 HashMap 来存储运算符的优先级。
  2. 运算符栈operators 栈用来存储遇到的运算符,并控制运算符的优先级顺序。
  3. 数字栈reverseResult 栈用来存储转换后的中缀表达式。
  4. RPN 转换:遍历输入的字符,按照运算符优先级和括号的规则,将表达式转换为后缀表达式。
  5. 计算后缀表达式:使用栈进行计算,遇到运算符时,从栈中弹出操作数并进行计算,最终返回结果。

时间复杂度

  • 时间复杂度:O(n),其中 n 是表达式的长度。每个字符都被访问一次,且栈的操作(入栈和出栈)都是常数时间操作。
  • 空间复杂度:O(n),需要额外的栈空间来存储操作符和操作数。

作者 : AWM