简单四则运算解析器 | 豆包MarsCode AI 刷题

146 阅读6分钟

Hello 小伙伴们大家好呀,历经小半个月猛刷70题MarsCode AI刷题,我终于获得入营资格啦!第一篇笔记,我想从我刷的代码题目入手。今天具体来撕一撕的相关问题。

简单四则运算解析器

题目相关链接先放在这边~简单四则运算解析器 - MarsCode

问题阐述

问题阐述:小F面临一个编程挑战:实现一个基本的计算器来计算简单的字符串表达式的值。该字符串表达式有效,并可能包含数字(0-9)、运算符+-及括号()。注意,字符串中不包含空格。除法运算应只保留整数结果。请实现一个解析器计算这些表达式的值,且不使用任何内置的eval函数。

题解

对于这个问题我们需要如何下手呢?

  1. 首先,题目根本目的是将字符串转化为数字形式,然后进行四则运算后输出最终结果。
  2. 那么,我们可以从四则运算的法则进行解析。优先级是这样的:有()则括号内数先进行计算;没有括号则先*/,后+-
  3. 好嘟,接下来怎么处理这个冗长的算术表达式字符串呢?我会考虑从栈入手,因为出栈和入栈涉及先后问题,遵循后进先出原则,这样可以处理运算先后问题。
  4. 我选择使用两个栈,一个栈numStack存放数字,一个栈opStack存放运算符号。
  • numStack规则:遍历字符串,如果当前字符是数字(isDigit(curChar)),那么使用int num = cur - '0';将字符转化为数字后,判断下一个字符是不是数字后【如果后一个是数字,那么就要将它与前一个结合起来形成一个完整的数字,如12+6,此时不能将1和2分开压入栈】,将处理过的数字压入numStack
  • opStack规则:
    • 如果当前字符是(,那么直接压入栈。
    • 如果是+or -or*or/,则需要比较它们的优先级【这里我将+-的优先级设为1,*/的优先级设为2,其他为0】,如果当前符号优先级小于栈顶符号,则将numStack栈顶的前两个数字push出去【第一个push出去的数字设为b,第二个设为a】、opStack栈顶的第一个符号push出去【设为op】,操作顺序是:a op b,然后将算出的结果压入numStack;一直循环【也就是调用performOperation并结果入numStack栈】直到opStack空或者当前字符优先级大于栈顶符号,然后跳出循环,将当前符号压入opStack
    • 如果当前字符是),那么只要opStack不为空并且栈顶符号部位左括号,就会一直重复计算操作performOperation并且将结果压入numStack。跳出循环后,将opStack栈顶符号也就是)弹出栈。
  1. 当所有字符都压入相应的栈后,只需要一直重复计算操作performOperation直到opStack为空,此时最终结果就存储在numStack的栈顶,答案只需要returnnumStack的栈顶就可以。

出栈入栈这边数字和运算符的操作会比较复杂凌乱。

import java.util.Stack;

public class Main {
    public static int solution(String expression) {
        Stack<Integer> numStack = new Stack<>();
        Stack<Character> opStack = new Stack<>();

        for (int i = 0; i < expression.length(); i++) {
            char cur = expression.charAt(i);

            if (Character.isDigit(cur)) {
                int num = cur - '0';
                while (i + 1 < expression.length() && Character.isDigit(expression.charAt(i + 1))) {
                    num = num * 10 + (expression.charAt(++i) - '0');
                }
                numStack.push(num);
            } else if (cur == '(') {
                opStack.push(cur);
            } else if (cur == ')') {
                while (!opStack.isEmpty() && opStack.peek() != '(') {
                    int result = performOperation(numStack, opStack);
                    numStack.push(result);
                }
                opStack.pop();
            } else if (isOperator(cur)) {
                while (!opStack.isEmpty() && precedence(opStack.peek()) >= precedence(cur)) {
                    int result = performOperation(numStack, opStack);
                    numStack.push(result);
                }
                opStack.push(cur);
            }
        }

        while (!opStack.isEmpty()) {
            int result = performOperation(numStack, opStack);
            numStack.push(result);
        }

        return numStack.pop();
    }

    private static boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/';
    }

    private static int precedence(char op) {
        switch (op) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
                return 2;
            default:
                return 0;
        }
    }

    private static int performOperation(Stack<Integer> numStack, Stack<Character> opStack) {
        int b = numStack.pop();
        int a = numStack.pop();
        char op = opStack.pop();
        switch (op) {
            case '+':
                return a + b;
            case '-':
                return a - b;
            case '*':
                return a * b;
            case '/':
                return a / b;
        }
        return 0;
    }

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

演算示例

用这里的测试用例举个例子,栈的状态用{}表示,设左到右分别对应栈底和栈顶。比如我们的string表达式是3+4*5/(3+2)

  1. 遍历string
  2. 当前字符为3,后一个字符不是数字,将3压入numStack。此时numStack状态:{3},opStack状态:{}。
  3. 当前字符为加号+,栈空,将+压入opStack。此时numStack状态:{3},opStack状态:{+}。
  4. 当前字符为4,后一个字符不是数字,将4压入numStack。此时numStack状态:{3, 4},opStack状态:{+}。
  5. 当前字符为乘号*,优先级高于栈顶符号,将*压入opStack。此时numStack状态:{3, 4},opStack状态:{+, *}。
  6. 当前字符为5,后一个字符不是数字,将5压入numStack。此时numStack状态:{3, 4, 5},opStack状态:{+, *}。
  7. 当前字符为除号/,优先级等于栈顶符号,进行performOperation调用:numStack中5和4出栈,opStack中乘号出栈;b=5,a=4,op=*,则4 * 5 = 20。将计算结果20压入numStack, / 压入opStack。此时numStack状态:{3, 20},opStack状态:{+, /}。
  8. 当前字符为左括号(,将 ( 压入opStack。此时numStack状态:{3, 20},opStack状态:{+, /, (}。
  9. 当前字符为3,后一个字符不是数字,将3压入numStack。此时numStack状态:{3, 20, 3},opStack状态:{+, /, (}。
  10. 当前字符为加号+,优先级高于栈顶符号,将+压入opStack。此时numStack状态:{3, 20, 3},opStack状态:{+, /, (, +}。
  11. 当前字符为2,后一个字符不是数字,将2压入numStack。此时numStack状态:{3, 20, 3, 2},opStack状态:{+, /, (, +}。
  12. 当前字符为右括号)opStack不为空且栈顶符号不为 ) ,则执行performOperation。执行后,numStack状态:{3, 20, 5},opStack状态:{+, /, (}。由于此时opStack栈顶为左括号 ( ,所以跳出循环,弹出 ( 。此时numStack状态:{3, 20, 5},opStack状态:{+, /}。
  13. string遍历结束
  14. 因为opStack不为空,所以执行performOperation直到opStack为空。
  • 第一次执行:numStack状态:{3, 4},opStack状态:{+}。
  • 第二次执行:numStack状态:{7},opStack状态:{}。
  1. 得到最终结果:7

小结

芜湖,手撕结束,心情舒畅!

关于栈还是有很多学问的,对于什么时候要用到、怎么用等问题,我也还在刷题体会中。

BTW,大家不要排斥用AI进行解答。实在是没思路的时候问问AI,它会从题目最直接的角度引领我们进行代码编写,然后我们在这个基础上对我们的代码再进行优化、改善、总结。总之,不要闭门造车!!!

那么,我们下期再见咯!!