DSA sheet · Stack · Expression evaluation pattern

Evaluate Reverse Polish Notation

The second problem of the expression evaluation pattern. Here the expression is written in a special order where the two numbers come first and the operator comes after them, like 2 1 + instead of 2 + 1. The teacher first solves it by editing the list in place (brute force, O(n²)), shows why a couple of plain variables are not enough, and then gives the stack solution, which is short and O(n). She even says it's simpler than Basic Calculator II from the previous video: in this notation there is no precedence to worry about at all.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Stacks and postfix expressions from scratch

What is a stack?

A stack is like a pile of books. You add a book on the top (push) and you remove the book on the top (pop). Looking at the top book without removing it is peek. The last one in is the first one out: LIFO (Last In, First Out).

stack operationPython listcost
push xst.append(x)O(1)
pop the topst.pop()O(1)
peekst[-1]O(1)
empty?not stO(1)

We draw stacks left = bottom, right = top. Popping or peeking an empty list raises IndexError, so normally you check if st: first. In this problem the input is guaranteed valid, which means there are always at least two numbers in the stack when an operator arrives (we rely on that).

Three ways to write the same expression

namewhere the operator goes"(2 + 1) × 3" written as
infix (normal maths)between the numbers(2 + 1) * 3
prefix (Polish)before the numbers* + 2 1 3
postfix (Reverse Polish)after the numbers2 1 + 3 *

The nice thing about postfix: it needs no brackets and no precedence rules. The order of the tokens already tells you exactly what to compute first. Every time you meet an operator, you apply it to the two most recent numbers that are still unused, and the result becomes a new number.

The expression evaluation pattern (stack version)

Order matters: which popped number is the left one?

For + and × the order doesn't matter, but for − and ÷ it does. The first pop gives the number that came later (the right operand). The second pop gives the earlier one (the left operand).

  tokens: 13  5  /        stack [13, 5]      top → | 5  |  ← b = pop()  (right)
                                                   | 13 |  ← a = pop()  (left)
  answer = a / b = 13 / 5

Python trap: division must truncate toward zero

The problem says division truncates toward zero (drop the decimals). Python's // rounds down instead, which is different for negative results.

a / btrue valuea // bint(a / b)
13 / 52.622
6 / −132−0.045−10
−7 / 2−3.5−4−3

Always write int(a / b) here. Numbers in this problem can be negative (tokens like "-11"), so this really happens. LeetCode's third example breaks if you use //.


Part A · Brute force: shrink the list in place

LeetCode 150

1The question in simple words

You get a list of strings tokens. Each token is either a whole number (maybe negative) or one of + - * /. The tokens form a valid expression in Reverse Polish Notation. Return its value as an integer.

ex 121+3*(2 + 1) × 3 = 9
ex 24135/+4 + (13 / 5) = 4 + 2 = 6
ex 310693+-11*/*17+5+= 22 (dry run in Part B)

Read ex 1 like this: "2 and 1 have come; now a + arrives, so add them → 3. Then 3 comes; now a * arrives, so multiply the two latest → 3 × 3 = 9."

2What the constraints tell us

3Intuition: why two variables are not enough

A first idea: walk through the tokens, keep the last two numbers in variables a and b, and when an operator comes, compute a op b. For ex 1 that works. But look at this input:

tokens213+*2 × (1 + 3) = 8

Three numbers arrive before the first operator. With only a and b, the 2 gets overwritten. The + gives 1 + 3 = 4, and then the * needs 4 and the 2, which we already lost. We need something that can hold any number of waiting numbers, without losing any.

The brute-force answer: don't store anything separately. Keep everything in the list itself and edit the list: when an operator is at index i, its two numbers are at i − 2 and i − 1. Replace those three tokens by one result.

4Building the logic

start213+*+ at i = 3 → use i−2 = 1 and i−1 = 3
after +24*4 written at i−2; i−1 and i removed. Now * at i = 2
after *8one token left → answer 8
Doubt 1: after removing, can the for loop just carry on?
→ No. The teacher shows the problem with ex 1. After "2 1 +" becomes "3", the list is [3, 3, *], length 3. But the for loop had planned to visit indexes up to 4 (the original length 5), and the next index it moves to doesn't line up with the shrunken list anymore. The loop ends (or points at the wrong place) while there is still work left: the * is never applied. The indexes got "disturbed" by the removal.
Doubt 2: so how do we fix it?
→ After each evaluation, break out of the for loop and start it again from the beginning on the new, shorter list. Wrap the for loop in while len(lst) > 1. When does it stop? A fully evaluated expression is one number. So "length bigger than 1" means "not finished yet".
Doubt 3: how do I know a token is an operator, given "-11" exists?
→ Check if the token is one of the four strings "+", "-", "*", "/". Don't test the first character, and don't use t.isdigit(): "-11".isdigit() is False, so a digit test would wrongly call -11 an operator.

5Approach steps

  1. Copy the tokens into a list we can edit.
  2. While the list has more than one item:
  3. Scan from i = 0. At the first operator, take a = lst[i-2], b = lst[i-1], compute a op b.
  4. Put the result at i − 2, remove positions i − 1 and i, then break to restart the scan.
  5. Return the single remaining value.

6Code (Python)

Brute force: edit the list, restart after each operator
class Solution:
    def evalRPN(self, tokens: list[str]) -> int:
        ops = {"+", "-", "*", "/"}
        lst = list(tokens)                    # a copy we are allowed to shrink
        while len(lst) > 1:                   # more than one item = not finished
            for i in range(len(lst)):
                t = lst[i]
                if t in ops:                  # the first operator we meet
                    a = int(lst[i - 2])       # left operand
                    b = int(lst[i - 1])       # right operand
                    if t == "+":
                        val = a + b
                    elif t == "-":
                        val = a - b
                    elif t == "*":
                        val = a * b
                    else:
                        val = int(a / b)      # truncate toward zero
                    lst[i - 2] = str(val)     # the result replaces the left operand
                    lst.pop(i)                # remove the operator
                    lst.pop(i - 1)            # remove the right operand
                    break                     # indexes changed: restart the scan
        return int(lst[0])

We pop index i before i − 1 so the second removal isn't shifted by the first. We store the result back as a string so the list stays all strings, like the input.

7Code line by line

linewhat it means
lst = list(tokens)The teacher converts the array into a list because we need to remove items. (In Python it's already a list. The copy just avoids changing the caller's input.)
while len(lst) > 1:Keep going until only the final answer is left.
if t in ops:Exact match with an operator string, so "-11" counts as a number.
a = int(lst[i - 2]) b = int(lst[i - 1])In postfix, the two numbers right before an operator are its operands. The earlier one is the left one.
val = int(a / b)Division that cuts toward zero.
lst[i - 2] = str(val) lst.pop(i); lst.pop(i - 1)Three tokens ("a b op") collapse into one token (the result).
breakThe removal shifted every later index, so the scan must restart.

8Dry run: ex 1, tokens = ["2", "1", "+", "3", "*"]

roundscan findsa, bresultlist after
1"+" at i = 22, 13["3", "3", "*"]
2"*" at i = 2 (scan restarted from 0)3, 39["9"]
stoplength is 1answer 9 ✓

9Complexity & remember

Remember the brute forceAt an operator at i: use i−2 and i−1, write the result at i−2, remove i−1 and i, break and restart. Loop while length > 1. O(n²) because of the rescans and the shifting.

Part B · Optimal: a stack of numbers

1The question in simple words

Same question. Now we want one pass over the tokens and no list removals.

2What the constraints tell us

3Intuition: a pile of waiting numbers

Look at what the brute force really did: it always used the two most recent unused numbers, and the result became the newest number. "Most recent first" is exactly what a stack gives us.

Take the teacher's example 2 3 1 * +: push 2, push 3, push 1 → [2, 3, 1]. At *, pop 1 and 3 → 3 × 1 = 3 → push → [2, 3]. At +, pop 3 and 2 → 2 + 3 = 5 → push → [5]. Answer 5. Nothing gets lost (the 2 waited patiently at the bottom), and nothing is removed from the middle of any list.

4Building the logic

Doubt 1: which pop is a and which is b?
→ b = st.pop() first (the later number, right side), then a = st.pop() (the earlier number, left side), and compute a op b. For ex 2, at "/" the stack is [4, 13, 5]: b = 5, a = 13, 13 / 5 = 2 ✓. If you swap them you get 5 / 13 = 0 ✗.
Doubt 2: why push the result back instead of keeping it in a variable?
→ The result is just a new number that a later operator may need, as in ex 2 where 2 is then added to 4. Putting it back on the stack treats it exactly like any other number.
Doubt 3: what do I store in the stack, strings or ints?
→ Convert number tokens with int(t) before pushing, so the stack only holds integers. int("-11") works fine.

5Approach steps

  1. Make an empty stack.
  2. For each token: if it's one of + − * /, pop b, pop a, push a op b.
  3. Otherwise push int(token).
  4. Return the only item left: st[-1] (or st.pop()).

6Code (Python)

Evaluate RPN with a stack (optimal)
class Solution:
    def evalRPN(self, tokens: list[str]) -> int:
        st = []
        for t in tokens:
            if t in ("+", "-", "*", "/"):
                b = st.pop()                  # right operand (came later)
                a = st.pop()                  # left operand (came earlier)
                if t == "+":
                    st.append(a + b)
                elif t == "-":
                    st.append(a - b)
                elif t == "*":
                    st.append(a * b)
                else:
                    st.append(int(a / b))     # truncate toward zero, NOT //
            else:
                st.append(int(t))             # a number waits on the stack
        return st[-1]

7Code line by line

linewhat it means
if t in ("+", "-", "*", "/"):Is this token an operator? Exact comparison, so "-11" goes to the number branch.
b = st.pop() a = st.pop()The top is the right operand, the one below it is the left operand.
st.append(a - b)Left minus right. Order matters.
st.append(int(a / b))Left divided by right, decimals dropped toward zero.
st.append(int(t))A number token: turn the string into an int and let it wait.
return st[-1]A valid expression leaves exactly one number: the answer. (For a single-token input like ["7"], that's just 7.)

8Dry run: ex 3

i0123456789101112
token10693+-11*/*17+5+
icurrent tokenwhat we pop (and why)what we pushstack AFTER (bottom → top)answer so far (top)
010nothing (a number waits)10[10]10
16nothing6[10, 6]6
29nothing9[10, 6, 9]9
33nothing3[10, 6, 9, 3]3
4+b = 3, a = 9 (operator needs the 2 latest)9 + 3 = 12[10, 6, 12]12
5-11nothing (a negative number, not an operator)−11[10, 6, 12, −11]−11
6*b = −11, a = 1212 × −11 = −132[10, 6, −132]−132
7/b = −132, a = 6int(6 / −132) = int(−0.045) = 0[10, 0]0
8*b = 0, a = 1010 × 0 = 0[0]0
917nothing17[0, 17]17
10+b = 17, a = 017[17]17
115nothing5[17, 5]5
12+b = 5, a = 1722[22]22 ✓

Step 7 is the trap: 6 // -132 in Python is −1, which would make step 8 give −10 and the final answer 12 ✗. With int(6 / -132) we get 0 and the correct 22.

after i = 5 (deepest)
10612−11
after i = 7
100
after i = 12
22

The red box is the top. The bottom of each picture is the bottom of the stack.

Ex 1 in short: push 2, push 1 → [2, 1]; "+" → 3 → [3]; push 3 → [3, 3]; "*" → 9 → [9]. Answer 9.

9Complexity & remember

RememberNumber → push. Operator → b = pop, a = pop, push a op b. Division: int(a / b). The last item on the stack is the answer.

Part C · Revision page

Brute force (edit the list)Stack
where waiting numbers livein the list itselfon the stack
at an operatoruse i−2 and i−1, write result at i−2, remove two items, restartpop b, pop a, push a op b
stopping rulelist length = 1tokens finished
timeO(n²) (rescans + shifting)O(n)
spaceO(n) copyO(n) stack
Basic Calculator II (previous problem)Evaluate RPN
inputinfix string, with spaces and multi-digit numberslist of tokens, already split
precedencemust be handled (* / now, + − later)none needed: the order already decides
stack holdssigned terms waiting to be addednumbers waiting for an operator
answersum of the stackthe single item left
If you remember only 5 lines 1. Postfix: the operator comes after its two numbers; no brackets, no precedence.
2. Two variables aren't enough (2 1 3 + * loses the 2). Use a stack.
3. Number → push int(t).
4. Operator → b = pop(), a = pop(), push a op b.
5. Divide with int(a / b). Answer = the last item.
Mistakes to avoid ✗ popping in the wrong order (b − a or b / a)
✗ using // (6 // −132 = −1, but the answer needs 0)
✗ testing t.isdigit() or t[0] == '-' to spot operators ("-11" is a number)
✗ pushing the string instead of int(t)
✗ in the brute force, continuing the for loop after a removal instead of restarting
test it yourself (paste under either solution)
s = Solution()
print(s.evalRPN(["2", "1", "+", "3", "*"]))     # 9
print(s.evalRPN(["4", "13", "5", "/", "+"]))    # 6
print(s.evalRPN(["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]))  # 22
print(s.evalRPN(["2", "1", "3", "+", "*"]))     # 8
print(s.evalRPN(["-7", "2", "/"]))              # -3
print(s.evalRPN(["7"]))                         # 7

Based on this video: Evaluate Reverse Polish Notation