21. Evaluate a Basic-Calculator-II-style arithmetic expression.
Given a string arithmetic expression containing nonnegative integers and operators such as plus, minus, multiply, and divide, return the evaluated integer result. Define whitespace handling, operator precedence, division behavior, invalid input assumptions, and complexity.
I scan the expression from left to right. I build one number at a time and apply the previous operator when I reach the next operator. I keep completed additive terms in total and the current term in last_term. Multiplication and division update last_term immediately, so precedence works without a stack. Division truncates toward zero. Finally, I return total + last_term. The solution takes O(n) time and O(1) auxiliary space.
See the Code while reading this explanation.
The input is a valid arithmetic-expression string containing nonnegative integers, spaces, and the operators +, -, *, and /. The output is the evaluated integer result. The main idea is to keep the newest term separate from the completed total. This lets multiplication and division change that term before addition or subtraction is finalized.
- What input sizes, value ranges, and edge cases should the solution handle?
- What output should be returned for empty, invalid, or duplicate input?
- Should I prioritize execution time or memory use, and may I use the standard library?
The function receives one expression string. Spaces are ignored. The expression has no parentheses. Multiplication and division have higher precedence than addition and subtraction. Division truncates toward zero. The diagram assumes valid input, so the function does not validate malformed expressions.
For the example, the input is "3 + 2*2 - 8/4". The returned result is 5.
I use four variables.
- total stores additive terms that are already complete.
- last_term stores the newest term. It can still change if the next operation is multiplication or division.
- current_number stores the number currently being read.
- operator stores the previous operator that must be applied to current_number.
The invariant is that total stores completed additive terms, while last_term stores the current term after any multiplication or division updates. Therefore, total + last_term equals the value of the processed part of the expression.
I start with total = 0, last_term = 0, current_number = 0, and operator = '+'.
I scan the expression from left to right. A digit updates current_number with current_number * 10 + int(ch). This builds multi-digit numbers. A space is skipped.
The code appends a final '+' sentinel. A sentinel is an extra operator used only to make the loop commit the last number.
When I reach an operator, I apply the previous operator to current_number.
For '+', I add the old last_term to total and start a new positive last_term.
For '-', I add the old last_term to total and start a new negative last_term.
For '*', I multiply last_term by current_number immediately.
For '/', I divide last_term by current_number and truncate toward zero. The implementation divides the absolute values with // and then restores the sign. This avoids floating-point conversion.
Then I save the new operator and reset current_number to 0.
Start with total = 0 and last_term = 0.
Read 3 and reach '+'. The previous operator is '+'. Before the update, the state is total = 0, last_term = 0, current_number = 3. The code performs total += 0 and last_term = 3. The state becomes total = 0, last_term = 3.
Read 2 and reach '*'. The previous operator is '+'. Before the update, the state is 0, 3, 2. The code performs total += 3 and last_term = 2. The state becomes 3, 2.
Read the next 2 and reach '-'. The previous operator is '*'. The code performs last_term = 2 * 2 = 4. The state becomes 3, 4.
Read 8 and reach '/'. The previous operator is '-'. The code performs total += 4 and last_term = -8. The state becomes 7, -8.
Read 4 and reach the sentinel at the end. The previous operator is '/'. The diagram shows int(-8 / 4) = -2. The integer-only implementation gets the same result by calculating abs(-8) // 4 = 2 and restoring the negative sign. The state becomes 7, -2.
Finally, the function returns total + last_term = 7 + (-2) = 5.
Addition and subtraction finalize the previous term by moving it into total. Multiplication and division update only last_term. This keeps higher-precedence work inside the current term before that term is added to total.
At every operator boundary, total + last_term equals the value of the processed prefix. After the sentinel commits the final number, this value equals the whole expression.
The loop processes each character at most once, so the time complexity is O(n), where n is the string length. The algorithm uses only a fixed number of variables, so the auxiliary space complexity is O(1).
Relevant cases are spaces, multi-digit numbers such as 14-3/2, chains such as 2*3*4, subtraction that creates a negative last_term, and an expression containing one number such as 42.
The key idea is to separate completed additive terms from the newest term. total stores terms that can no longer change. last_term stores the current term, which may still be multiplied or divided. When the previous operator is + or -, the old last_term is moved into total and a new signed term begins. When the operator is * or /, only last_term changes. This preserves precedence without a stack. The invariant is that total + last_term equals the value of the processed prefix.
def calculate(expression: str) -> int:
# Stores additive terms that are already complete.
total = 0
# Stores the newest term, which may still change after * or /.
last_term = 0
# Builds the current one-digit or multi-digit number.
current_number = 0
# Treat the first number as a positive term.
operator = "+"
# Add a sentinel operator so the final number is committed.
for ch in expression + "+":
# Ignore whitespace.
if ch == " ":
continue
# Build a multi-digit number from left to right.
if ch.isdigit():
current_number = current_number * 10 + int(ch)
continue
# Apply the previous operator to current_number.
if operator == "+":
total += last_term
last_term = current_number
elif operator == "-":
total += last_term
last_term = -current_number
elif operator == "*":
last_term *= current_number
else: # operator == "/"
# Divide absolute values, then restore the sign.
# This truncates toward zero without using floating point.
quotient = abs(last_term) // current_number
last_term = quotient if last_term >= 0 else -quotient
# Save the new operator and prepare for the next number.
operator = ch
current_number = 0
# Add the final current term to all completed terms.
return total + last_term
if __name__ == "__main__":
expression = "3 + 2*2 - 8/4"
print(calculate(expression)) # 5Let n be the number of characters in the expression. The algorithm scans from left to right and processes each character at most once, so the time complexity is O(n). It stores only total, last_term, current_number, operator, quotient, and the loop character. The amount of extra memory does not grow with n, so the auxiliary space complexity is O(1).
This pattern is useful in simple calculator features, expression evaluators, configuration parsers, and interview problems that need operator precedence without parentheses. It works when the supported operations are addition, subtraction, multiplication, and division.
This problem tests whether the candidate can preserve operator precedence during one left-to-right scan. It also checks state design, multi-digit parsing, whitespace handling, signed intermediate terms, division semantics, and careful Python implementation. The interviewer wants to see a clear invariant, a walkthrough that matches the code, accurate reasoning about the previous operator, and correct O(n) time and O(1) auxiliary-space analysis.
A common mistake is adding every number directly into total, which loses multiplication and division precedence. Another mistake is applying the new operator instead of the previous operator at an operator boundary. Candidates may forget to process the final number, so the sentinel is important. Using a // b directly when a is negative is wrong because // floors instead of truncating toward zero. Other mistakes are not resetting current_number and not skipping spaces.
State the invariant before coding: total contains completed additive terms, while last_term contains the current term that multiplication or division may still change. Then connect every operator case to that invariant.









