Evaluate the value of an arithmetic expression in Reverse Polish Notation.
Valid operators are +
, -
, *
, /
. Each operand may be an integer or another expression.
Note:
- Division between two integers should truncate toward zero.
- The given RPN expression is always valid. That means the expression would always evaluate to a result and there won't be any divide by zero operation.
Example 1:
Input: ["2", "1", "+", "3", "*"] Output: 9 Explanation: ((2 + 1) * 3) = 9
Example 2:
Input: ["4", "13", "5", "/", "+"] Output: 6 Explanation: (4 + (13 / 5)) = 6
Example 3:
Input: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"] Output: 22 Explanation: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22
解法:
此题第一直觉是利用双指针相向移动原理,圈定出每一个操作符对应的两端操作数,但是其中一端或两端有可能是表达式,而非数字,所以需要用递归的重复上面的步骤。最后从里到外计算每个子表达式的值,最后到达最外层即可得出最终结果。此方法需要大量的指针位置处理和计算工作,实现起来太过复杂。
第二直觉是从左到右遍历数组,当遇到操作符时,将该字符索引前的两位分别当做左操作数和右操作数,然后结合相应的操作符进行计算,将原数组的三位合并成一位,并存入计算结果,然后继续向下遍历。这样最后只需要计算数组的第0位到第2位的表达式即可得到最后结果。这个方法的问题是表达式合并要么需要移动数组后面的元素,要么需要置入标志字符以区分合并产生的空位。无论用那种方法都会导致引入新的循环从而增加了算法的时间复杂度。
根据第二直觉的算法,可以想到大体的思路是依次计算出表达式里的每一个子表达式,然后将子表达式的结果和其前后的值继续一起运算,直至数组末尾。那么为了能够随时获得子表达式的值的前后值,可以利用栈数据结构。具体算法如下:
- 从0位开始遍历数组
- 遇到数字边把其值入栈
- 遇到操作符则连续出栈两个元素(这两个元素即为操作符两端的数字),然后将计算结果入栈
- 直至数组遍历完毕,最后一次计算表达式的结果即为最终结果
代码如下:
public static int evalRPN(String[] tokens) {
if(tokens.length == 1) {
return Integer.parseInt(tokens[0]) ;
}
int res = 0 ;
Stack<Integer> stack = new Stack<>() ;
int p = 0 ;
while(p != tokens.length) {
if(tokens[p].equals("+") || tokens[p].equals("-") || tokens[p].equals("*") || tokens[p].equals("/")) {
int right = stack.pop() ;
int left = stack.pop() ;
res = calExp(left, right, tokens[p]) ;
stack.push(res) ;
} else {
stack.push(Integer.parseInt(tokens[p])) ;
}
p++ ;
}
return res ;
}
private static int calExp(int left, int right, String op) {
switch(op) {
case "+": return left + right ;
case "-": return left - right ;
case "*": return left * right ;
case "/": return left / right ;
default:
return 0 ;
}
}