算法编程题-计算器系列

原题描述

计算器一般就是给定一个用字符串表示的算术表达式,一般包括加减乘除四种运算符,允许负数存在,求这个表达式的值,一般符号还包括括号等字符。这是比较经典的一类题目,在leetcode上有三道这样的题目,链接如下:
224. 基本计算器
227 基本计算器II
面试题16.26 计算器

思路简述

这类题目都需要用到栈这种数据结构,本科期间的数据结构课讲到栈的应用时,有提及一种方法,就是先将这种表达式通过栈转换为后缀表达式,然后用一个栈即可进行求解。更加通用的办法是使用两个栈,一个栈用来存储数字,另外一个栈用来存储符号。
具体的思路这里就不展开讲了,可以参考下面的代码实现,这里分享几点编码技巧:

  1. 负数处理。我采取的处理负数的方式是将负数-a看成是一个表达式0-a,注意到,负数只会在一个表达式的开头或者括号中式子的开头出现,所以遇到这种情况,直接往数字栈中增加一个数字0。
  2. 递归处理。递归处理真的是处理复杂问题的利器,遇到括号怎么办,经典的思路是将括号也作为符号,然后搞什么符号的优先级,这里我是将括号里的式子递归地调用自身处理,大大简化了代码
  3. 公共函数抽离:对于一些公共逻辑,尽可能采用抽离函数的形式简化代码,也能增加代码的可读性。比如,笔者的代码中将提取数字这个操作就抽离出来作为一个公共函数
  4. 具体计算过程:当遍历计算表达式的过程中,遇到乘除运算,可以将栈中有的乘除运算全部算完,遇到加减运算,可以将栈顶的所有乘除运算算完。

代码

其中,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)

扩展

相似的题目如下,感兴趣的读者可以自行探索。
在这里插入图片描述

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值