实现一个基本的计算器 | 豆包MarsCode AI 刷题

112 阅读7分钟

在编程学习的过程中,解决实际问题是提升编程能力的重要途径。最近,我在豆包MarsCode AI刷题平台上遇到了一个有趣的挑战:实现一个基本的计算器,用于计算简单的字符串表达式的值。这个问题不仅考验了我的编程能力,还让我深入理解了表达式解析的基本原理。以下是我对这个问题的解析、学习心得以及高效学习方法的总结。

题目解析

题目要求实现一个计算器,能够处理包含数字(0-9)、运算符(+、-、、/)及括号的字符串表达式。需要注意的是,字符串中不包含空格,且除法运算应只保留整数结果。通过这个题目,我意识到实现一个计算器的核心在于如何正确解析表达式并按照运算优先级进行计算。

思路

  1. 使用栈结构:为了处理运算符的优先级和括号的嵌套关系,我决定使用两个栈:一个用于存储操作数(数字),另一个用于存储运算符。

  2. 遍历表达式:我将遍历整个表达式字符串,逐个字符进行处理:

  • 如果是数字,直接入栈。

  • 如果是运算符,判断其优先级,并根据优先级进行计算。

  • 如果遇到左括号,直接入运算符栈;遇到右括号时,进行计算直到遇到左括号。

3. 处理剩余运算符:在遍历结束后,处理栈中剩余的运算符,完成最终的计算。

代码详解

以下是我实现的计算器代码:

import java.util.Stack;

public class Main { public static int solution(String expression) { // 初始化操作数栈和运算符栈 Stack numStack = new Stack<>(); Stack opStack = new Stack<>();

    // 遍历表达式
    for (int i = 0; i < expression.length(); i++) {
        char ch = expression.charAt(i);
        
        if (Character.isDigit(ch)) {
            // 处理数字
            int num = ch - '0'; // 将字符转换为数字
            numStack.push(num); // 将数字压入操作数栈
        } else if (ch == '+' || ch == '-' || ch == '*' || ch == '/') {
            // 处理运算符
            while (!opStack.isEmpty() && precedence(opStack.peek()) >= precedence(ch)) {
                char op = opStack.pop(); // 弹出栈顶运算符
                int b = numStack.pop(); // 弹出栈顶操作数
                int a = numStack.pop(); // 弹出次栈顶操作数
                numStack.push(applyOp(a, b, op)); // 计算并将结果压入操作数栈
            }
            opStack.push(ch); // 将当前运算符压入运算符栈
        } else if (ch == '(') {
            // 处理左括号
            opStack.push(ch); // 将左括号压入运算符栈
        } else if (ch == ')') {
            // 处理右括号
            while (!opStack.isEmpty() && opStack.peek() != '(') {
                char op = opStack.pop(); // 弹出运算符
                int b = numStack.pop(); // 弹出栈顶操作数
                int a = numStack.pop(); // 弹出次栈顶操作数
                numStack.push(applyOp(a, b, op)); // 计算并将结果压入操作数栈
            }
            if (!opStack.isEmpty() && opStack.peek() == '(') {
                opStack.pop(); // 弹出左括号
            }
        }
    }
    
    // 处理剩余的运算符
    while (!opStack.isEmpty()) {
        char op = opStack.pop(); // 弹出运算符
        int b = numStack.pop(); // 弹出栈顶操作数
        int a = numStack.pop(); // 弹出次栈顶操作数
        numStack.push(applyOp(a, b, op)); // 计算并将结果压入操作数栈
    }
    
    // 返回操作数栈中的唯一元素,即表达式的计算结果
    if (numStack.isEmpty()) {
        throw new RuntimeException("操作数栈为空");
    }
    return numStack.pop(); // 返回计算结果
}

private static int precedence(char op) {
    // 定义运算符的优先级
    switch (op) {
        case '+':
        case '-':
            return 1; // 加法和减法优先级最低
        case '*':
        case '/':
            return 2; // 乘法和除法优先级较高
        default:
            return 0; // 无效运算符
    }
}

private static int applyOp(int a, int b, char op) {
    // 根据运算符执行相应的计算
    switch (op) {
        case '+':
            return a + b; // 加法
        case '-':
            return a - b; // 减法
        case '*':
            return a * b; // 乘法
        case '/':
            return a / b; // 整数除法
        default:
            throw new IllegalArgumentException("无效的运算符: " + op);
    }
}

public static void main(String[] args) {
    // 测试用例
    System.out.println(solution("1+1") == 2); // 输出: true
    System.out.println(solution("3+4*5/(3+2)") == 7); // 输出: true
    System.out.println(solution("4+2*5-2/1") == 12); // 输出: true
    System.out.println(solution("(1+(4+5+2)-3)+(6+8)") == 23); // 输出: true
    System.out.println(solution("2*(5+5*2)/3+(6+8*3)") == 40); // 输出: true
}

}

代码解释

  1. 栈的初始化:我们使用两个栈,一个 numStack 用于存储操作数(数字),另一个 opStack 用于存储运算符。栈的使用使得我们能够方便地处理运算符的优先级和括号的嵌套。

  2. 遍历表达式:我们逐个字符遍历输入的表达式:

  • 数字处理:当遇到数字时,我们将其转换为整数并压入 numStack。

  • 运算符处理:当遇到运算符时,我们需要检查当前运算符的优先级。如果栈顶运算符的优先级高于或等于当前运算符的优先级,我们就弹出栈顶运算符,进行计算,并将结果压入 numStack。然后将当前运算符压入 opStack。

  • 括号处理:当遇到左括号时,直接将其压入 opStack;当遇到右括号时,我们弹出运算符并进行计算,直到遇到左括号。

  1. 处理剩余运算符:在遍历结束后,我们需要处理 opStack 中剩余的运算符,依次进行计算。

4. 返回结果:最终,numStack 中将只剩下一个元素,即表达式的计算结果。

知识总结

通过这个题目,我总结了以下几个知识点:

1. 栈的应用:栈是一种后进先出(LIFO)的数据结构,非常适合处理运算符的优先级和括号的嵌套。通过使用栈,我们能够在遇到运算符时,快速判断其优先级并进行相应的计算。

  1. 运算符优先级:在计算表达式时,必须考虑运算符的优先级,确保先计算高优先级的运算。通过定义一个 precedence 方法,我们可以轻松地管理不同运算符的优先级。

3. 字符串解析:在处理字符串时,逐个字符解析是常见的做法,尤其是在处理数学表达式时。通过遍历字符串,我们能够有效地识别数字、运算符和括号,并进行相应的处理。

4. 异常处理:在编写代码时,考虑到可能出现的异常情况是非常重要的。在本例中,我们在处理运算符时,添加了对无效运算符的异常处理,以确保程序的健壮性。

学习计划

结合豆包MarsCode AI的刷题功能,我制定了以下高效学习计划:

1.制定刷题计划:每天安排一定时间进行刷题,选择不同类型的题目,确保覆盖广泛的知识点。通过定期的练习,我能够巩固所学知识,并提高解决问题的能力。

2. 利用错题进行针对性学习:每次完成一组题目后,分析错题,找出知识盲点,并进行针对性复习。通过对错题的深入分析,我能够更好地理解自己的不足,并加以改进。

3.总结与反思:每周进行一次总结,记录学习进度和遇到的问题,调整学习策略。通过定期的反思,我能够更清晰地认识到自己的学习效果,并及时调整学习方法。

工具运用

我将豆包MarsCode AI的刷题功能与其他学习资源相结合,以达到更好的学习效果。例如,使用在线编程平台进行代码测试,查阅相关算法和数据结构的书籍,观看视频教程等。这种多元化的学习方式帮助我更全面地理解编程知识。

结论

通过实现这个基本的计算器,我不仅提高了自己的编程能力,还加深了对表达式解析的理解。这个过程让我认识到,编程不仅仅是写代码,更是解决问题的思维过程。希望我的经验和学习方法能够帮助其他入门同学更有效地学习编程。继续努力,编程之路任重而道远!在未来的学习中,我将继续探索更复杂的算法和数据结构,提升自己的编程能力。