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

55 阅读8分钟

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

简单四则运算解析器

问题描述

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


测试样例

样例1:

输入:expression = "1+1"
输出:2

样例2:

输入:expression = "3+4*5/(3+2)"
输出:7

样例3:

输入:expression = "4+2*5-2/1"
输出:12

样例4:

输入:expression = "(1+(4+5+2)-3)+(6+8)"
输出:23

样例5:

输入:expression = "2*(5+5*2)/3+(6+8*3)"
输出:40

解题思路

实现一个基本的计算器来解析和计算字符串表达式的值,我们需要遵循运算符的优先级规则,即先乘除后加减,并且括号内的运算优先级最高

eval 函数的作用是执行一个字符串表达式,并返回表达式的值。这个字符串表达式可以包含变量、运算符、函数调用等,eval 会将其视为代码并执行。

如果允许使用 eval 来解决问题的话,依然需要小心,eval(expression) 得到的是一个浮点数,而后台的样例中要求使用整数的除法,所以再进行eval之前需要把 expression 中的除法 / 换成 //

def solution(expression):
    # 将所有的除法运算符 '/' 替换为地板除法运算符 '//'
    modified_expression = expression.replace('/', '//')
    # 使用 eval 函数计算修改后的表达式的值
    return eval(modified_expression)

if __name__ == "__main__":
    # 测试用例
    print(solution("9/(3+2)-4") == -3)  # 现在应该输出 True
    print(solution("1+1") == 2)  # 应该输出 True
    print(solution("3+4*5/(3+2)") == 7)  # 应该输出 True
    print(solution("4+2*5-2/1") == 12)  # 应该输出 True
    print(solution("(1+(4+5+2)-3)+(6+8)") == 23)  # 应该输出 True
    print(solution("2*(5+5*2)/3+(6+8*3)") == 40)  # 应该输出 True

我们不使用 eval 函数,自己解析 expression

要实现表达式的解析,我们首先需要按顺序遍历 expression 字符串,并准备两个栈,一个用来放数字 values,一个用来存放符号 ops

python 中可以直接把列表作为栈使用

如果遍历到数字,就看看数字之后还有没有数字,把它们组成完整的整数,直到遇到运算符或括号,再把这整个数压入 values

如果遍历到操作符(+-*/),则比较其优先级与 ops 栈顶元素的优先级:如果 ops 栈顶元素的优先级更高或相等,且不是(,则先计算 ops 栈顶的操作符。然后将当前操作符压入 ops

如果遍历到 (,直接压入ops

如果遍历到 ),计算ops直到遇到 (,并将结果压入values

遍历完成后,计算 ops 中的所有运算,并将结果压入 values

values 的栈顶为最终结果

思路验证

Prompt

需要一点思路提示

MarsCode AI

要解决这个计算器问题,我们可以采用以下思路:

1. 理解问题

我们需要解析一个包含数字、运算符(+-*/)和括号的字符串表达式,并计算其值。表达式中的运算符优先级需要正确处理,括号内的表达式需要优先计算。

2. 数据结构选择
  • :栈是一种非常适合处理括号和运算符优先级的数据结构。我们可以使用两个栈,一个用于存储操作数(数字),另一个用于存储运算符。
  • 优先级表:我们可以定义一个运算符优先级表,用于决定何时进行运算。
3. 算法步骤
  1. 初始化栈:创建两个栈,一个用于操作数(num_stack),另一个用于运算符(op_stack)。

  2. 遍历表达式:从左到右遍历表达式中的每个字符。

    • 如果当前字符是数字,继续读取直到遇到非数字字符,然后将完整的数字压入num_stack

    • 如果当前字符是运算符或括号:

      • 如果op_stack为空或当前运算符优先级高于栈顶运算符,直接将当前运算符压入op_stack
      • 否则,弹出op_stack中的运算符并进行计算,直到满足上述条件。
      • 如果是左括号(,直接压入op_stack
      • 如果是右括号),弹出op_stack中的运算符并进行计算,直到遇到左括号(
  3. 计算表达式:遍历结束后,如果op_stack中还有运算符,继续进行计算,直到op_stack为空。

  4. 返回结果:最终num_stack中剩下的唯一元素就是表达式的结果。

4. 处理细节
  • 运算符优先级:定义运算符优先级表,例如*/的优先级高于+-
  • 括号处理:遇到左括号时,直接压入栈;遇到右括号时,弹出运算符直到遇到左括号。

通过以上步骤,我们可以逐步解析并计算表达式的值。希望这些思路能帮助你更好地理解和解决这个问题。

代码实现

def solution(expression):
    # 定义运算符的优先级
    def precedence(op):
        if op in ('+', '-'):
            return 1
        if op in ('*', '/'):
            return 2
        return 0

    # 应用操作符到操作数
    def apply_op(ops, values):
        op = ops.pop()  # 弹出操作符
        right = values.pop()  # 弹出右边的操作数
        left = values.pop()  # 弹出左边的操作数
        if op == '+':
            values.append(left + right)  # 执行加法
        elif op == '-':
            values.append(left - right)  # 执行减法
        elif op == '*':
            values.append(left * right)  # 执行乘法
        elif op == '/':
            values.append(left // right)  # 执行整除

    # 解析表达式
    def parse_expression(expression):
        values = []  # 操作数栈
        ops = []  # 操作符栈
        i = 0
        while i < len(expression):
            if expression[i].isdigit():  # 如果是数字
                num = 0
                while i < len(expression) and expression[i].isdigit():
                    num = num * 10 + int(expression[i])  # 构建整数
                    i += 1
                values.append(num)  # 将数字压入操作数栈
            elif expression[i] == '(':  # 如果是左括号
                ops.append(expression[i])  # 将左括号压入操作符栈
                i += 1
            elif expression[i] == ')':  # 如果是右括号
                while ops and ops[-1] != '(':  # 遇到右括号,计算直到遇到左括号
                    apply_op(ops, values)
                if ops:
                    ops.pop()  # 弹出左括号
                i += 1
            elif expression[i] in ('+', '-', '*', '/'):  # 如果是操作符
                while ops and ops[-1] != '(' and precedence(ops[-1]) >= precedence(expression[i]):
                    apply_op(ops, values)  # 计算优先级更高的操作符
                ops.append(expression[i])  # 将当前操作符压入操作符栈
                i += 1
        while ops:  # 计算剩余的操作符
            apply_op(ops, values)
        return values[0]  # 返回计算结果

    return parse_expression(expression)  # 返回解析和计算的结果

if __name__ == "__main__":
    # 测试用例
    print(solution("1+1") == 2)
    print(solution("3+4*5/(3+2)") == 7)
    print(solution("4+2*5-2/1") == 12)
    print(solution("(1+(4+5+2)-3)+(6+8)") == 23)
    print(solution("2*(5+5*2)/3+(6+8*3)") == 40)

复杂度分析

对于整个算法,分为几个步骤,扫描字符串,解析字符,计算

扫描字符串的时间复杂度为O(n)O(n),即字符串的长度

解析字符,代码中每扫描一个字符就进行一次解析,每次解析都进行时间复杂度为 O(1)O(1) 的入栈,出栈或计算操作。最多进行了 n 次解析,时间复杂度为 O(n)O(n)

计算,每次为时间复杂度为 O(1)O(1) 的入栈,出栈或计算操作,最多进行了 n 次计算,总的时间复杂度为 O(n)O(n)

因此代码中的时间复杂度为 O(n)O(n)

以下由AI进行时间复杂度分析,判断自己的分析是否正确

Prompt

计算编辑区代码的复杂度

MarsCode AI

要计算编辑区代码的时间复杂度和空间复杂度,我们需要分析代码的主要部分和它们的工作方式。

时间复杂度
1. parse_expression 函数

这个函数是核心部分,负责解析和计算表达式。

  • 遍历表达式while i < len(expression) 循环会遍历表达式中的每个字符,时间复杂度为 O(n),其中 n 是表达式的长度。
  • 处理数字:在遍历过程中,如果遇到数字,会继续读取直到遇到非数字字符,这部分的时间复杂度也是 O(n)
  • 处理运算符和括号:每次遇到运算符或括号时,可能会进行多次栈操作(压栈、弹栈),但由于每个字符最多只会被处理一次,这部分的时间复杂度也是 O(n)

综合来看,parse_expression 函数的时间复杂度为 O(n)

2. apply_op 函数

这个函数在每次调用时会执行一次运算,时间复杂度为 O(1)。由于每次运算符栈中的运算符都会被处理一次,最多会有 O(n) 次调用,因此这部分的时间复杂度也是 O(n)

空间复杂度
1. parse_expression 函数
  • 操作数栈 values:在最坏情况下,所有数字都会被压入栈中,空间复杂度为 O(n)
  • 操作符栈 ops:在最坏情况下,所有运算符和括号都会被压入栈中,空间复杂度为 O(n)

综合来看,parse_expression 函数的空间复杂度为 O(n)

总结
  • 时间复杂度O(n),其中 n 是表达式的长度。
  • 空间复杂度O(n),其中 n 是表达式的长度。

希望这些分析对你理解代码的复杂度有所帮助!

知识点总结

这个题目实际上就是将中缀表达式,通过栈转化成后缀表达式进行计算。

逆波兰表示法(Reverse Polish Notation,简称 RPN),也称为后缀表达式,是一种无需括号来标识操作符优先级的数学表达式书写方式。在逆波兰表示法中,每个操作符都置于其操作数之后。这种表示法的主要优点是无歧义和易于计算,因为不需要括号来明确指出运算的顺序。