基本计算器

Posted by zhouqian on Friday, June 24, 2022

力扣 224. 基本计算器

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。


方案一:将中缀表达式转为后缀表达式计算(此方案适用于基本计算器 1-3 三题)。

func calculate(s string) int {
    // 结尾补一个空格,保证所有数字都被计算到
    // 空格可以触发后面代码中 for 循环中将数字入栈的操作
    s += " "

    // 将中缀表达式转为后缀表达式,并保存到 tokens
    tokens := make([]string, 0, len(s))

    // 标识符,用作判断是否需要补 0,用作类似 -1 + 5 => 0 - 1 + 5 的场景
    needsZero := true
    // 保存遍历字符串遇到的符号
    // 因为存在括号,所以需要将部分符号临时保存到 opStack
    opStack := make([]string, 0, len(s))
    // 保存最近的数字,用字符串格式是为了便于整合完整数字并加入 tokens 数组
    num := ""
    // 遍历字符串
    for _, x := range s {
        // 如果是数字则暂作保留并立即进行下一轮便利(因为需要截取完整的数字)
        if '0' <= x && x <= '9' {
            num += string(x)
            needsZero = false
            continue
        } 
        // 如果缓存了数字
        if len(num) > 0 {
            // 将数字保存到 tokens 中,并将 num 变量初始化
            tokens = append(tokens, num)
            num = ""
        }

        // 如果遇到空格则略过,不能放在对缓存数字 num 的判断之前
        // 因为代码开头在字符串 s 的末尾加了空格,遍历到此空格时,会正好触发上面的将缓存数字 num 入栈的操作
        if x == ' ' {
            continue
        }

        // 左括号直接入栈
        if x == '(' {
            opStack = append(opStack, string(x))
            // 左括号后如果出现符号,则需要补零,如 1 + (-2 + 3)
            needsZero = true
            continue
        }
        // 右括号则将运算符号栈出栈,直至遇到左括号
        if x == ')' {
            for len(opStack) > 0 && opStack[len(opStack) - 1] != "(" {
                tokens = append(tokens, string(opStack[len(opStack) - 1]))
                opStack = opStack[:len(opStack) - 1]
            }
            // 将左括号出栈
            opStack = opStack[:len(opStack) - 1]
            //右括号后面出现符号只可能是运算符
            needsZero = false
            continue
        }
        
        // 如果需要补零
        // 该操作需要在判断运算符的操作之前
        if needsZero {
            tokens = append(tokens, "0")
            needsZero = false
        }

        // 判断当前运算符号优先级
        curRank := getRank(string(x))
        // 遍历符号栈,如果栈顶元素优先级大于当前元素,则将其加入 tokens
        // 比如, 2+3*5+4,遍历到 + 时,则需要将此前的 * 提取出来加入 tokens
        // 因为遇到 + 时,高优先级的内容已经计算完了
        // tokens:  [2, 3, 5]
        // opStack: [+, *]
        // ==> tokens: [2, 3, 5, *, +], opStack: []
        for len(opStack) > 0 && getRank(opStack[len(opStack) - 1]) >= curRank {
            tokens = append(tokens, string(opStack[len(opStack) - 1]))
            opStack = opStack[:len(opStack) - 1]
        }
        // 将当前运算符入栈
        opStack = append(opStack, string(x))
    }

    // 将符号栈内的剩余符号入栈
    for len(opStack)  > 0 {
        tokens = append(tokens, string(opStack[len(opStack) - 1]))
        opStack = opStack[:len(opStack) - 1]
    }   
    return evalRPN(tokens)
}

// getRank 判断运算符等级,乘除高于加减。
func getRank(op string) int {
    switch op {
        case "+", "-":
            return 1
        case "*", "/":
            return 2
    }
    return 0
} 


func evalRPN(tokens []string) int {
    // 定义栈
    stk := make([]int, 0, len(tokens))

    // 遍历数组
    for _, token := range tokens {
        // 判断是否是数字
        num, err := strconv.Atoi(token)
        // 将数字入栈
        if err == nil {
            stk = append(stk, num)
            continue
        }
        
        // 将最近两个数字取出并出栈
        num1, num2 := stk[len(stk) - 2], stk[len(stk) - 1]
        stk = stk[:len(stk) - 2]
        // 将最近两个数字通过运算符计算并将其入栈
        stk = append(stk, calc(num1, num2, token))
    }
    // 因为题目保证给定表达式必定有结果,所以直接返回栈中第一个元素
    return stk[0]
}

func calc(a, b int, op string) int {
    switch op {
        case "+":
            return a + b
        case "-":
            return a - b
        case "*":
            return a * b
        case "/":
            return a / b
    }
    return 0
}
  • 时间复杂度:O(n),因为需要遍历数组中所有元素,虽然遍历途中有对括号之类的出栈操作遍历(每个元素的入栈出栈至多一次),但是均摊后时间复杂度还是在 O(n)。
  • 空间复杂度:O(n),因为使用了数个长度不超过 n 的栈辅助计算,所以空间复杂度为 O(n)。