leetcode150. 逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的运算符包括 + , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
- 整数除法只保留整数部分。
- 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
- 该算式的逆波兰表达式写法为 ( ( 1 2 + )( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
思路
看一下本题,「其实逆波兰表达式相当于是二叉树中的后序遍历」。可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。
例: 2 + 4 的波兰表达式为 24+, 形象为二叉树如图:
得到的后序24+, 就是波兰表达式.
但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后续遍历的方式把二叉树序列化了,就可以了。
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么栈与队列:匹配问题都是栈的强项非常像了.
运算动画如下:
这和1047. 删除字符串中的所有相邻重复项是差不多的,只不过本题不要相邻元素做消除了,而是做运算!
C++代码
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st; //注意存储int型变量, 我们只将数值入栈, 遇到运算符就取栈顶元素进行运算
for (int i = 0; i < tokens.size(); i++)
{
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/")
{
//遇到运算符取栈顶元素
int num1 = st.top(); //注意num1是运算符右边的数
st.pop();
int num2 = st.top(); //num2是运算符左边的数
st.pop();
//将运算结果重新入栈
if (tokens[i] == "+") st.push(num2 + num1);
else if (tokens[i] == "-") st.push(num2 - num1);
else if (tokens[i] == "*") st.push(num2 * num1);
else if (tokens[i] == "/") st.push(num2 / num1); //因为提莫说明了表达式总会得出有效数值且不存在除数为 0 的情况。因此这里不考虑num1为0的情况.
}
else
{
//当前遍历字符为数值,直接入栈
st.push(stoi(tokens[i])); //stoi 将字符串转换成10进制的int
}
}
int result = st.top();
st.pop();
return result;
}
};
Carl的题外话
我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。
例如:4 + 13 / 5,这就是中缀表达式,计算机从左到右去扫描的话,扫到13,还要判断13后面是什么运算法,还要比较一下优先级,然后13还和后面的5做运算,做完运算之后,还要向前回退到 4 的位置,继续做加法,你说麻不麻烦!
那么将中缀表达式,转化为后缀表达式之后:[“4”, “13”, “5”, “/”, “+”] ,就不一样了,计算机可以利用栈里顺序处理,不需要考虑优先级了。也不用回退了, 「所以后缀表达式对计算机来说是非常友好的。」
可以说本题不仅仅是一道好题,也展现出计算机的思考方式。
Carl哥的公众号:代码随想录