[Leetcode 227] 基础计算器 II

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
s 由整数和算符 ('+', '-', '*', '/') 组成,中间由一些空格隔开。
整数除法仅保留整数部分。
 
你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。
 
示例 1:
示例 2:
示例 3:
说明:
  • 1 <= s.length <= 3 * 105
  • s 表示一个 有效表达式
  • 表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1] 内
  • 题目数据保证答案是一个 32-bit 整数
 

解法一:栈 (推荐)

这道题的表达式没有括号,只有 + - * /,所以只需要处理乘除优先级。
加减的优先级最低,因此我们可以把整个表达式理解成:若干个“已经处理完乘除的加法项”之和。
比如:3 + 2 * 2 可以看成 3 + 4
我们用一个栈就可以处理优先级:加减直接作为加法项入栈;乘除立即和栈顶合并;最后栈求和。
也就是,
  • 遇到 +-,直接把当前数字作为正数或负数压栈,用栈保存这些“加法项”;
  • 遇到 */,说明当前数字要先和栈顶的上一项结合,算完以后再把结果放回栈里。
  • 最后,把栈里所有项求和,就是答案。
我们从左到右扫描字符串,维护三个变量:
sign 的含义非常重要。它不是当前字符,而是上一个运算符,也就是“应该如何处理刚刚读完的这个数字”。
比如扫描到 3 + 2 * 2
当我们读完第一个 3,遇到 + 时,就根据之前的 sign = '+'3 压栈。然后把 sign 更新成 +,表示下一个数字 2 前面的运算符是加号。
当读完第二个 2,遇到 * 时,根据 sign = '+'2 压栈。然后把 sign 更新成 *
最后读完第三个 2 时,根据 sign = '*',把栈顶 2 弹出,与当前数字 2 相乘,得到 4 再压回栈。
此时栈是 [3, 4],求和得到 7
有两点注意:
  1. 结尾要额外处理
    1. 表达式里的数字通常是在遇到下一个运算符时才被处理的。可是最后一个数字后面没有运算符。
      有两种写法:
    2. 在循环里判断 i == len(s) - 1
    3. 给字符串末尾补一个 +,统一触发最后一个数字的处理。
    4. 第二种逻辑更干净。
  1. 除法不能用 //
    1. 题目要求除法向 0 截断。
      比如:5 / 2 -> 2-5 / 2 -> -2
      但 Python 的 // 是向下取整:-5 // 2 == -3 ,所以这里不能直接用 //
      更简单的写法是 int(prev / num),它会向 0 截断,符合题目要求。
复杂度分析
  • 时间复杂度: O(n),其中 n 是字符串长度。我们只扫描一遍表达式。
  • 空间复杂度: O(n)。最坏情况下表达式全是加减,例如:1+2-3+4-5
    • 每个数字都会作为一个独立加法项进入栈。

解法二:常数空间优化 (最优解)

栈写法最后会做 sum(stack) ,但其实我们不一定要把所有加法项都存下来。
栈的作用:
  • 对于 + 和 ,旧的上一项已经确定,不会再被乘除修改;
  • 对于 和 /,只会影响最近的那一项,也就是栈顶。
所以我们可以不用栈,而是维护两个变量:
当遇到 +- 时,说明前一个 lastNumber 已经彻底结束,可以加入 res。然后当前数字成为新的 lastNumber
当遇到 */ 时,不能动 res,只需要更新 lastNumber
最后返回 res + lastNumber
Walk Through Example
3 + 2 * 2为例:
先读到 3,后面遇到 +,此时 lastNumber = 3。当进入 + 逻辑时,把旧的 lastNumber 加入 res,于是 res = 3,新的 lastNumber = 2
接着遇到 *2 还不能加入 res,因为后面要乘。读到最后一个 2 后,执行乘法 lastNumber = 2 * 2 = 4 ,最终返回 res + lastNumber = 3 + 4 = 7
复杂度分析
  • 时间复杂度: O(n),仍然只扫描一次字符串。
  • 空间复杂度: O(1),只使用常数个变量。

总结

这题没有括号,所以主要问题是处理乘除优先级。
这两种解法本质上是同一个思路。
  • 栈版本把表达式转换成一堆加法项:3 + 2 * 2 - 4 / 2,会被处理成 [3, 4, -2],最后求和。
  • 常数空间版本则不保存完整列表,而是把已经确定的项提前加入 res,只保留最后一个还可能被乘除影响的项 lastNumber
可以这样理解:
  • 栈:保存所有加法项,最后 sum。
  • 常数空间:已确定的项直接加到 res,只保留最后一项。
Buy Me a Coffee
上一篇
[Leetcode 208] 实现 Trie
下一篇
[Leetcode 236] 二叉树的最近公共祖先