《剑指 Offer》专项突破版 - 面试题 36 : 详解后缀表达式(C++ 实现)
题目链接:LCR 036. 逆波兰表达式求值 - 力扣(LeetCode)
题目:
后缀表达式是一种算术表达式,它的操作符在操作数的后面。输入一个用字符串数组表示的后缀表达式,请输出该后缀表达式的计算结果。假设输入的一定是有效的后缀表达式。例如,后缀表达式 ["2", "1", "3", "*", "+"] 对应的算术表达式是 "2 + 1 * 3",因此输出它的计算结果 5。
分析:
后缀表达式又叫逆波兰表达式(Reverse Polish Notation, RPN),是一种将操作符放在操作数后面的算术表达式。通常用的是中缀表达式,即操作符位于两个操作数的中间,如 "2 + 1 * 3"。使用后缀表达式的好处是不需要使用括号。例如,中缀表达式的 "2 + 1 * 3" 和 "(2 + 1) * 3" 不相同。它们的后缀表达式分别为 "213*+" 和 "21+3*"。后缀表达式不使用括号也能无歧义地表达这两个不同的算术表达式。
面试小提示:
后缀表达式对于很多人而言可能是一个比较陌生的概念。应聘者在面试的时候遇到新的概念是很常见的。面试官有时故意提出新概念,用来考查应聘者的学习能力。在面试的时候如果遇到不太熟悉的概念,应聘者一定要先确保自己正确理解了这个概念,再动手做题。如果有不理解的地方,应聘者可以提出自己的疑问让面试官提供详细的信息,然后应聘者再列举几个例子向面试官描述自己的理解。如果面试官确认理解是正确的,应聘者再着手做题也不迟。应聘者一定不要害怕向面试官提出问题。能提出有针对性的问题是学习能力的重要体现,在面试过程中这是一个加分项。
下面以 ["2", "1", "3", "*", "+"] 为例,分析后缀表达式的计算过程。从左到右扫描这个数组。首先遇到的是操作数 "2",由于这是后缀表达式,操作符还在后面。不知道操作符就不能做计算,于是先将 "2" 保存到某个数据容器中。接下来的两个还是操作数,"1" 和 "3",由于缺少操作符,因此还是不知道如何计算,只好也将它们先后保存到数据容器中。接下来遇到了一个操作符 "*"。按照后缀表达式的规则,这个操作符对应的操作数是 "1" 和 "3",于是将它们从数据容器中取出来。此时容器中有先后保存的 "2"、"1" 和 "3" 这 3 个操作数,此时取出的是后保存的两个,最先保存的 "2" 仍然留在数据容器中。这看起来是 "后进先出" 的顺序,所以可以考虑用栈来实现这个数据容器。
由于当前的操作符是 "*",因此将两个操作数 "1" 和 "3" 相乘,得到结果 "3"。这个结果可能会成为后面操作符的操作数,因此仍然将它入栈。最后遇到的是操作符 "+",此时栈中有两个操作数,即 "2" 和 "3",分别将它们出栈,然后计算它们的和,得到 "5",再将结果 "5" 入栈。此时整个后缀表达式已经计算完毕,留在栈中的唯一的操作数 "5" 就是结果。
代码实现:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for (const string& s : tokens)
{
if (s == "+" || s == "-" || s == "*" || s == "/")
{
int rightOperand = st.top(); // 右操作数
st.pop();
int leftOperand = st.top(); // 左操作数
st.pop();
switch (s[0])
{
case '+':
st.push(leftOperand + rightOperand);
break;
case '-':
st.push(leftOperand - rightOperand);
break;
case '*':
st.push(leftOperand * rightOperand);
break;
case '/':
st.push(leftOperand / rightOperand);
break;
}
}
else
{
st.push(stoi(s));
}
}
return st.top();
}
};
由于栈中只保存操作数,操作符不需要保存到栈中,因此上述代码创建的是一个整数型栈。上述代码逐一扫描后缀表达式数组中的每个字符串,如果遇到的是一个操作数,则将其入栈;如果遇到的是一个操作符,则两个操作数出栈并执行相应的运算,然后计算结果入栈。