以字符串形式评估数学表达式

时间:2017-12-06 03:20:01

标签: algorithm stack

这是一个标准的面试问题,评估以字符串形式给出的数学表达式。

如此提供,'3+3-6*2' 答案应该是-6

现在,如果表达式仅支持四个操作+,-,*,/ 那么有一种使用堆栈的简单方法。

我已经通过将中缀符号转换为postfix然后使用堆栈解决它来解决它 - 但我正在寻找一种仅支持这四种操作的不同方法。这是我的解决方案,

def evaluate(self, s: str) -> int:
    expr = [i for i in re.split(r'(\d+|\W+)', s) if i]
    rpn = self.convertToPostfix(expr)
    return self.evalPostfix(rpn)

def convertToPostfix(self, infix: list) -> list:
    stack = collections.deque()
    result = []
    for item in infix:
        if item.isdigit():
            result.append(item)
        else:
            while len(stack) > 0 and self.has_higher_precedence(stack[-1], item):
                result.append(stack[-1])
                stack.pop()
            stack.append(item)
    while len(stack) > 0:
        result.append(stack.pop())
    return result

def has_higher_precedence(self, a: str, b: str) -> bool:
    if a == '/' and b == '*' or b == '+' or b == '-':
        return True
    if a == '*' and b == '+' or '-':
        return True
    if a == '+' and b == '-':
        return True
    return False

def evalPostfix(self, p: list) -> int:
    stack = collections.deque()
    for item in p:
        if item.isdigit():
            stack.append(int(item))
        elif item[1:].isdigit():
            stack.append(int(item))
        else:
            curr = stack.pop()
            prev = stack.pop()
            if item == '+':
                total = prev + curr
            elif item == '-':
                total = prev - curr
            elif item == '*':
                total = prev * curr
            else:
                total = prev / curr
            stack.append(total)
    return stack.pop()

我也知道这可以通过设计一个递归的词法解析器来解决,但这超出了这个问题的范围。

所以我的问题是,如果只有四个运算符,有没有一种简单的方法可以在O(n)时间内使用堆栈

3 个答案:

答案 0 :(得分:2)

您可以将字符串存储为字符列表,然后线性地运行2次,首先计算乘法/除法然后加/减。

即。在第一次迭代中有

1+10/2 -> 1 + 5 

然后

1 + 5 -> 6

这也可以通过堆栈轻松实现,您可以使用+或 - 符号向其添加数字。当您添加到堆栈并意识到已达到/*符号时,您弹出上一个元素,将其乘以或除以当前数字,然后将其推回。最后,您可以找到堆栈中所有元素的总和。

更进一步,您可以观察到在前面的示例中,正在使用的堆栈的唯一元素是直接在答案之前的元素。这表明您可以在保持总和的同时存储前一个元素,如果您达到*/符号,则从总和中减去前一个元素并添加更新的元素。这种方法有效地解决了O(1)额外存储的O(n)扫描中的问题,使其成为解决该问题的最佳解决方案。

答案 1 :(得分:1)

您可以修改Shunting-yard算法以便在一次通过中进行解析和评估。不需要转换为后缀的中间步骤。将问题限制在没有括号的四个标准数学运算符中并不会改变基本方法。

这是标准的Shunting-yard算法,来自链接文章。我在下面添加了行号以供参考:

 1: while there are tokens to be read:
 2: read a token.
 3: if the token is a number, then push it to the output queue.
 4: if the token is an operator, then:
 5:     while (there is an operator at the top of the operator stack with
 6:         greater precedence) or (the operator at the top of the operator stack has
 7:                        equal precedence and
 8:                        the operator is left associative) and
 9:                      (the operator at the top of the stack is not a left bracket):
10:             pop operators from the operator stack, onto the output queue.
11:     push the read operator onto the operator stack.
12: if the token is a left bracket (i.e. "("), then:
13:     push it onto the operator stack.
14: if the token is a right bracket (i.e. ")"), then:
15:     while the operator at the top of the operator stack is not a left bracket:
16:         pop operators from the operator stack onto the output queue.
17:     pop the left bracket from the stack.
        /* if the stack runs out without finding a left bracket, then there are
18:     mismatched parentheses. */
19: if there are no more tokens to read:
20:     while there are still operator tokens on the stack:
21:         /* if the operator token on the top of the stack is a bracket, then
22:         there are mismatched parentheses. */
23:         pop the operator onto the output queue.
24: exit.

修改涉及用操作数堆栈替换输出队列:整数堆栈。因此,例如,在第3行而不是将数字推送到输出队列,您将数字推送到操作数堆栈。这当然要求您将字符转换为整数。

然后,在第10行,您从运算符堆栈弹出运算符并推送到输出队列,而是:

  1. 弹出操作员
  2. 从操作数堆栈中弹出两个操作数
  3. 执行操作
  4. 将结果推回操作数堆栈
  5. 用相同的逻辑替换第16和23行。

    如果在你评估的任何时候,操作数堆栈上没有足够的操作数,那么你就有不匹配的运算符:类似2 + 3 + - (除非你决定支持一元+和一元 - ) ,或2 * / 3。

    当您到达第24行时,运算符堆栈为空,操作数堆栈应包含单个值:最终结果。如果操作数堆栈上有多个项目,那么操作数太多(不应该发生)。

    因此,使用操作数堆栈中的弹出替换第24行,您可以将其作为结果返回。

    Shunting-yard实际上是"递归词法解析器的简化版本"你提到过的。但是,不是通过递归构建堆栈帧,而是直接操作堆栈。

    修改代码来做到这一点不应该太困难。以下是convertToPostfix函数的修改版本,名为evaluateInfix,只需一次完成即可。请注意,我不是Python程序员,所以我不能保证代码能够完美地工作,但它应该给你一个想法:

    def evaluateInfix(self, infix: list) -> int:
        operatorStack = collections.deque()
        operandStack = collections.deque()
        result = []
        for item in infix:
            if item.isdigit():
                val = convertDigitToNumber(item)  // however that's done
                // push to operand stack
                operandStack.append(val)
            else:
                while len(operatorStack) > 0 and self.has_higher_precedence(operatorStack[-1], item):
                    // pop two operands from stack, evaluate, and push result
                    // call this "pop and eval"
                    op2 = operandStack[-1]
                    operandStack.pop()
                    op1 = operandStack[-1]
                    operandStack.pop()
                    operator = operatorStack[-1]
                    operatorStack.pop()
                    val = evaluate(operator, op1, op2) // this function evaluates op1 <operator> op2
    
                    // push result back onto operand stack
                    operandStack.append(val)
                operatorStack.append(item)
        while len(operatorStack) > 0:
            // insert "pop and eval" code here
        // at this point there should be a single value on the operand stack
        return operandStack[-1]
    

    evaluate函数接受一个操作符和两个操作数,执行操作,并返回结果。因此,在('+', 3, 5)的情况下,它会计算3+5,并返回8

    这里的想法是,无论何时进行评估,您都需要两个操作数和一个操作员。

    测试用例:3+5*2

           operand    operator
    char    stack      stack
    --------------------------
      3     [3]       []
      +     [3]       [+]
      5     [3,5]     [+]
      *     [3,5]     [+,*]
      2     [3,5,2]   [+,*]
     <end>
     at this point, we're at the end of the string.
     Pop 2 and 5 from the operand stack, pop * from the operator stack,
     and evaluate
            [3,10]    [+]
     pop and eval again
            [13]      []
     operator stack is empty. Pop result from operand stack and return.
    

答案 2 :(得分:0)

为了完成起见,将@Jim Mischel解决方案发布为Python 3代码。

import re


class Solution:
    def evalExpression(self, s):
        operator_stack, operand_stack = [], []
        tokens = [i for i in re.split(r'(\d+|\W+)', s) if i]
        for token in tokens:
            if token.isdigit():
                operand_stack.append(int(token))
            else:
                while len(operator_stack) > 0 and self.has_higher_precedence(operator_stack[-1], token):
                    val = self.evaluate(operand_stack.pop(), operator_stack.pop(), operand_stack.pop())
                    operand_stack.append(val)
                operator_stack.append(token)
        while len(operator_stack) > 0:
            val = self.evaluate(operand_stack.pop(), operator_stack.pop(), operand_stack.pop())
            operand_stack.append(val)
        return operand_stack[-1]

    def has_higher_precedence(self, opr1, opr2):
        if opr1 == '/' or opr1 == '*' and opr2 == '+' or opr2 == '-':
            return True
        return False

    def evaluate(self, a, b, c):
        if b == '/':
            return c/a
        elif b == '*':
            return c*a
        elif b == '-':
            return c-a
        else:
            return c+a


if __name__ == '__main__':
    solution = Solution()
    print(solution.evalExpression('3+3-6*2'))