大家好,我是怒码少年小码。
相信学过数据结构的伙计们都知道栈的一类经典题目就是表达式计算,今天我们就好好聊聊。
计算器问题
LeetCode 227:给你一个字符串表达式s,请实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。假设这个表达式始终是有效合法的。
示例:
- 输入:s=“3+2*2”
- 输出:7
这时有小伙伴可能就会想到使用eval()
,那么如果我要求不允许使用任何将字符串作为数学表达式计算的内置函数,例如eval()
,阁下又该如何应对?😎
且听我缓缓道来。
首先第一个问题就是如何判断数字和运算符,这个简单,isdigit
函数是C++的标准库函数之一,用于判断一个字符是否是数字字符。
既然有运算,那就一定有先后。我们可以先计算乘除运算。并将结果放回到一个地方。然后计算加减,众所周知,减法就是加法,我们可以把要进行减法的数字置为负数,最后所有的数字进行遍历加法后就可以得到结果了。
class Calculate {
public:
int calculate(string s) {
stack<int>numStack;
int num = 0;
char sign = '+';
for (int i = 0; i < s.length(); i++) {
char c = s[i];
if (isdigit(c)) {
num = num * 10 + (c - '0');
}
//遇到运算符或者到达字符串结尾时计算结果
if (!isdigit(c) && c != ' ' || i == s.length() -1) {
if (sign == '+') {
numStack.push(num);
}
else if (sign == '-') {
numStack.push(-num);
}
else if (sign == '*') {
int prevNum = numStack.top();
numStack.pop();
numStack.push(prevNum * num);
}
else if (sign == '/') {
int prevNum = numStack.top();
numStack.pop();
numStack.push(prevNum / num);
}
sign = c;
num = 0;
}
}
int result = 0;
while (!numStack.empty()) {
result += numStack.top();
numStack.pop();
}
return result;
}
};
在calculate
方法内部,我们声明了一个stack<int>
类型的numStack
来存储数字,一个int
类型的num
来存储当前的数字,一个char
类型的sign
记录每个数字之前的运算符。第一个数字之前如果没有符号,默认为加号。
接下来,我们循环从左到右遍历字符串s
中的每个字符。对于每个字符,我们检查它是否是数字。如果是数字,我们将其转换为整数并将其追加到当前数字num
的末尾,使用num = num * 10 + (c - '0')
实现。
当遇到运算符或者到达字符串结尾时,我们需要根据之前存储的运算符sign
来计算结果。加号:直接压入栈中;减号:将该数字的相反数压入栈中;乘除号:用栈顶元素与该数字计算,并用结果覆盖栈顶元素
因为是从左到右遍历,此时有一个数字之前的运算符是*或/号,说明该数字需要和它之前的数字进行运算,由于栈后进先出的特点,它之前的数字就保留在栈顶。
处理完之后更新sign
,重置num = 0;
,以便在下一次循环中重新计算新的数字。
最后,我们通过将数字栈中的所有数字相加来计算最终结果,并将其返回。
注意:这段代码并没有处理括号和运算优先级的情况,题目也没做要求。现在测试一下吧:
int main() {
string s = "3+2*2";
Calculate c;
int result = c.calculate(s);
cout << result << endl;//输出7
}
这里编写了一个类Calculate
而没有直接定义方法calculate
,是使用了面向对象编程的思想。就好像是一个计算器(如测试用例中的c
)中有一个功能叫计算字符串一样,刚好合题。
END
还有一道很经典的逆波兰表达式,但是涉及二叉树,我们后面再讲。已经不是“酒香不怕巷子深”的时代了,伙计们要是喜欢的话,可以分享给你的朋友,或者点一个免费的赞赞❤️。