原题描述
计算器一般就是给定一个用字符串表示的算术表达式,一般包括加减乘除四种运算符,允许负数存在,求这个表达式的值,一般符号还包括括号等字符。这是比较经典的一类题目,在leetcode上有三道这样的题目,链接如下:
224. 基本计算器
227 基本计算器II
面试题16.26 计算器
思路简述
这类题目都需要用到栈这种数据结构,本科期间的数据结构课讲到栈的应用时,有提及一种方法,就是先将这种表达式通过栈转换为后缀表达式,然后用一个栈即可进行求解。更加通用的办法是使用两个栈,一个栈用来存储数字,另外一个栈用来存储符号。
具体的思路这里就不展开讲了,可以参考下面的代码实现,这里分享几点编码技巧:
- 负数处理。我采取的处理负数的方式是将负数-a看成是一个表达式0-a,注意到,负数只会在一个表达式的开头或者括号中式子的开头出现,所以遇到这种情况,直接往数字栈中增加一个数字0。
- 递归处理。递归处理真的是处理复杂问题的利器,遇到括号怎么办,经典的思路是将括号也作为符号,然后搞什么符号的优先级,这里我是将括号里的式子递归地调用自身处理,大大简化了代码
- 公共函数抽离:对于一些公共逻辑,尽可能采用抽离函数的形式简化代码,也能增加代码的可读性。比如,笔者的代码中将提取数字这个操作就抽离出来作为一个公共函数
- 具体计算过程:当遍历计算表达式的过程中,遇到乘除运算,可以将栈中有的乘除运算全部算完,遇到加减运算,可以将栈顶的所有乘除运算算完。
代码
其中,gulc.Stack是自定义实现的栈,读者可以自行实现,也可以参考导入笔者的工具包gulc。
import (
"strings"
"gulc"
)
type ICalculator interface {
Calculate(exp string) int
}
type Calculator struct {
}
func NewCalculator() ICalculator {
return &Calculator{}
}
func (c *Calculator) Calculate(exp string) int {
exp = strings.Replace(exp, " ", "", -1) // 消除所有的空格
numStack := gulc.NewStack[int]() // 封装好的泛型栈, 数字栈
opeStack := gulc.NewStack[byte]() // 运算符号栈
k := 0
for k < len(exp) {
if exp[k] >= '0' && exp[k] <= '9' { // 数字
num := c.ParseNum(exp, &k)
numStack.Push(num)
} else if exp[k] == '(' { // 遇到括号递归处理
exp := c.ParseExp(exp, &k)
val := c.Calculate(exp)
numStack.Push(val)
} else { // 遇到运算符号
if exp[k] == '-' && (k == 0 || exp[k - 1] == '(') { // 针对负数特殊处理
numStack.Push(0)
}
if exp[k] == '+' || exp[k] == '-' { // 此时堆在运算符栈里的都可以进行运算
if !opeStack.IsEmpty() {
c.Compute(numStack, opeStack, -1)
}
opeStack.Push(exp[k])
} else {
if !opeStack.IsEmpty() && opeStack.Top() != '+' && opeStack.Top() != '-' {
c.Compute(numStack, opeStack, 1)
}
opeStack.Push(exp[k])
}
k++
}
}
c.Compute(numStack, opeStack, -1) // 将剩下的全部运算完
return numStack.Top()
}
// ParseNum 从exp的pos位置开始提取一个数字
func (c *Calculator) ParseNum(exp string, pos *int) int {
num := 0
for *pos < len(exp) && exp[*pos] >= '0' && exp[*pos] <= '9' {
num = num * 10 + int(exp[*pos] - '0')
(*pos)++
}
return num
}
// ParseExp 提取括号内的一个表达式,用于递归处理
func (c *Calculator) ParseExp(exp string, pos *int) string {
i := *pos
j := i + 1
lbCount := 1 // 记录左括号的相对数量
for j < len(exp) && (exp[j] != ')' || lbCount != 1) {
if exp[j] == '(' {
lbCount++
} else if exp[j] == ')' {
lbCount--
}
j++
}
*pos = j + 1
return exp[i + 1: j]
}
// Compute 执行计算,limit表示执行次数,为负数表示执行完所有的运算符号
func (c *Calculator) Compute(numStack *gulc.Stack[int], opeStack *gulc.Stack[byte], limit int) {
var compute func(numStack *gulc.Stack[int], opeStack *gulc.Stack[byte]) = func(numStack *gulc.Stack[int], opeStack *gulc.Stack[byte]) {
v1 := numStack.Pop()
v2 := numStack.Pop()
ope := opeStack.Pop()
switch ope {
case '+':
v1 = v1 + v2
case '-':
v1 = v2 - v1
case '*':
v1 = v1 * v2
case '/':
v1 = v2 / v1
}
numStack.Push(v1)
}
if limit == 1 {
compute(numStack, opeStack)
return
}
for !opeStack.IsEmpty() {
compute(numStack, opeStack)
}
}
leetcode运行截图如下:

复杂度分析
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
扩展
相似的题目如下,感兴趣的读者可以自行探索。

290

被折叠的 条评论
为什么被折叠?



