Problem overview
In reverse Polish notation, also called postfix notation, an operator comes after its two operands. The expression 3 + 4 is written 3 4 +, and (1 + 2) * 3 is written 1 2 + 3 *. No parentheses are needed: the order of the tokens alone says what is computed first.
The function evalRPN(tokens []string) int receives a valid expression as a slice of strings and returns its value. Each token is one of the operators "+", "-", "*", "/", or an integer such as "42" or "-7". An operator works on the two values right before it: the first is its left operand and the second its right operand, so 8 3 - is 8 - 3 = 5. Division truncates toward zero, so 7 / 2 is 3 and -7 / 2 is -3.
For ["2", "1", "+", "3", "*"] the answer is 9, because (2 + 1) * 3 = 9. For ["4", "13", "5", "/", "+"] it is 6, because 13 / 5 truncates to 2 and 4 + 2 = 6.
The lesson asks for O(n) time and O(n) extra space, where n is the number of tokens.
Approach: a stack of values
Read the tokens from left to right. A number can't be used until an operator asks for it, so it waits. When an operator arrives, the two values it needs are the two most recent values that haven't been used yet. That is exactly the top of a stack: the last value pushed is the first one taken off.
The rule is short:
- A number: push it.
- An operator: pop two values, combine them, and push the result back. The result takes the place of its two operands, so a later operator can use it.
Every operator turns two values into one. A valid expression therefore ends with exactly one value on the stack, and that is the answer.
Here is the stack for ["2", "1", "+", "3", "*"] after each token:
2 [2]
1 [2 1]
+ [3]
3 [3 3]
* [9]
The + pops 1 and 2 and pushes 3. The * pops 3 and 3 and pushes 9.
The order of the two pops matters. The right operand was pushed last, so it comes off first. Name it b, pop the left operand into a second, and compute a - b or a / b: for 8 3 - that gives 8 - 3, not 3 - 8.
Step by step
- Create the stack:
stack := []int{}, an empty slice of integers. Its end is the top of the stack:appendpushes there, and reslicing pops from there. - Loop over the tokens with
for _, token := range tokens. Each token is read once, so the whole evaluation isO(n). - Test for an operator by comparing
tokenwith"+","-","*"and"/", joined with||. A negative number like"-7"starts with a minus sign, but it is not equal to the one-character token"-". - Pop the right operand:
b := stack[len(stack)-1]reads the top, andstack = stack[:len(stack)-1]drops it. - Pop the left operand into
athe same way. - Add: if
token == "+",stack = append(stack, a+b). The result goes back on the stack, andappend's result must be stored. - Subtract: an
else iffor"-"pushesa-b. The order matters here. - Multiply: an
else iffor"*"pushesa*b. - Divide: the last
elsecovers"/"and pushesa/b. - Parse a number: the
elseof the firstifhandles every other token.num, _ := strconv.Atoi(token)converts the string to anint. The blank identifier ignores the error, since the remaining tokens are valid integers. - Push it:
stack = append(stack, num). - Return the result: after the loop,
stack[len(stack)-1]is the one value left.
The solution in Go
func evalRPN(tokens []string) int {
stack := []int{}
for _, token := range tokens {
if token == "+" || token == "-" || token == "*" || token == "/" {
b := stack[len(stack)-1]
stack = stack[:len(stack)-1]
a := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if token == "+" {
stack = append(stack, a+b)
} else if token == "-" {
stack = append(stack, a-b)
} else if token == "*" {
stack = append(stack, a*b)
} else {
stack = append(stack, a/b)
}
} else {
num, _ := strconv.Atoi(token)
stack = append(stack, num)
}
}
return stack[len(stack)-1]
}
The Go details worth a closer look:
- A slice as a stack.
append(stack, v)pushes.stack[:len(stack)-1]is the slice without its last element and pops without copying anything. Go has no built-in stack type, and this is the usual idiom. - Integer division truncates toward zero. In Go,
-7 / 2is-3and7 / -2is-3, exactly as the lesson requires, so no adjustment is needed. For comparison,-7 % 2is-1: the remainder takes the sign of the dividend. strconv.Atoireturns two values. It returns the number and an error."-11"parses to -11 with anilerror, and a leading minus sign is fine."+"would fail withstrconv.Atoi: parsing "+": invalid syntaxand return 0, which is why the operators are handled before the parsing.- Unused values. Go refuses to compile unused variables, so the ignored error is written as
_instead of a name. intis 64 bits. The lesson guarantees every value fits in 32 bits, so the solution never needs to worry about overflow. In Go,2147483647 + 1in anintis simply 2147483648.- Strings compare by value.
token == "+"compares the contents, so it works as expected.
Complexity
Time: O(n). Each token is read once, and each push, pop and arithmetic operation takes constant time. Parsing a number costs time proportional to its length, which is bounded because the integers fit in 32 bits.
Space: O(n). In the worst case, such as 1 2 3 4 + + +, nearly all the numbers are on the stack before the first operator arrives.
Edge cases and common mistakes
- A single number.
["42"]gives 42 and["-7"]gives -7. There is no operator, and the stack holds one value. - Operand order.
["8", "3", "-"]gives 5. Popping intoafirst andbsecond computes3 - 8and gives -5. - Negative numbers.
"-11"is a number, not an operator. The comparison with"-"is against the whole token, so it works. - Division toward zero.
["7", "2", "/"]gives 3,["-7", "2", "/"]gives -3,["7", "-2", "/"]gives -3 and["-7", "-2", "/"]gives 3. A language that floors instead would give -4 for the second one. - A result of zero from a small fraction.
["6", "-132", "/"]gives 0, because the fraction of about -0.045 is truncated. - Forgetting to store
append's result. Writingappend(stack, a+b)alone is a compile error in Go, because the result is not used. The grown slice must be assigned back tostack. - Using the wrong end. The values to combine are at the end of the slice. Reading
stack[0]takes the oldest value, not the newest. - Treating tokens as characters. A token like
"13"has two characters, so the code can't look at one byte. It compares whole strings and parses withstrconv.Atoi.
Testing
["2", "1", "+", "3", "*"]gives9.["4", "13", "5", "/", "+"]gives6.["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]gives22.["8", "3", "-"]gives5.["7", "2", "/"]gives3, and["-7", "2", "/"]gives-3.["42"]gives42.["6", "-132", "/"]gives0.
