Taro Logo

Basic Calculator II

Medium
Meta logo
Meta
1 view
Topics:
StringsStacks

Given a string s which represents an expression, evaluate this expression and return its value. The integer division should truncate toward zero. You may assume that the given expression is always valid. All intermediate results will be in the range of [-2^31, 2^31 - 1]. You are not allowed to use any built-in function which evaluates strings as mathematical expressions, such as eval().

For example:

  1. If s = "3+2*2", the output should be 7.
  2. If s = " 3/2 ", the output should be 1.
  3. If s = " 3+5 / 2 ", the output should be 5.

Constraints:

  • 1 <= s.length <= 3 * 10^5
  • s consists of integers and operators ('+', '-', '*', '/') separated by some number of spaces.
  • s represents a valid expression.
  • All the integers in the expression are non-negative integers in the range [0, 2^31 - 1].
  • The answer is guaranteed to fit in a 32-bit integer.

Solution


Naive Approach

A straightforward approach is to parse the string and evaluate it step by step based on operator precedence. We can first handle multiplication and division, and then addition and subtraction.

Algorithm:

  1. Remove spaces from the input string.
  2. Iterate through the string, performing multiplication and division first.
  3. Iterate again, performing addition and subtraction.

Code (Python):

def calculate_naive(s):
    s = s.replace(' ', '')
    if not s:
        return 0

    nums = []
    ops = []
    num = 0
    i = 0
    while i < len(s):
        if s[i].isdigit():
            num = num * 10 + int(s[i])
        elif s[i] in '+-*/':
            nums.append(num)
            ops.append(s[i])
            num = 0
        i += 1
    nums.append(num)

    i = 0
    while i < len(ops):
        if ops[i] == '*' or ops[i] == '/':
            num1 = nums[i]
            num2 = nums[i+1]
            op = ops[i]
            if op == '*':
                res = num1 * num2
            else:
                res = num1 // num2 if num1 >= 0 else -(-num1 // num2)
            nums[i+1] = res
            del nums[i]
            del ops[i]
            i -= 1
        i += 1

    i = 0
    while i < len(ops):
        num1 = nums[i]
        num2 = nums[i+1]
        op = ops[i]
        if op == '+':
            res = num1 + num2
        else:
            res = num1 - num2
        nums[i+1] = res
        del nums[i]
        del ops[i]
        i -= 1
        i += 1

    return nums[0]

Time Complexity: O(n), where n is the length of the string. We iterate through the string multiple times.

Space Complexity: O(n) in the worst case due to storing numbers and operators in lists.

Optimal Approach (Using Stack)

A more efficient approach involves using a stack to keep track of numbers and perform operations based on precedence.

Algorithm:

  1. Initialize a stack to store numbers.
  2. Iterate through the string.
  3. If the character is a digit, accumulate the number.
  4. If the character is an operator (+, -, ", /) or it's the end of the string, perform the following:
    • If the previous operator was " or /, perform the corresponding operation with the top of the stack and the current number, and push the result back onto the stack.
    • Otherwise, push the current number onto the stack with the appropriate sign (positive or negative based on the previous operator).
  5. After processing the entire string, sum up the numbers in the stack to get the final result.

Code (Python):

def calculate(s):
    stack = []
    num = 0
    op = '+'
    s += '+0'  # append a dummy operation to trigger last calculation

    for char in s:
        if char.isdigit():
            num = num * 10 + int(char)
        elif char in '+-*/':
            if op == '+':
                stack.append(num)
            elif op == '-':
                stack.append(-num)
            elif op == '*':
                stack.append(stack.pop() * num)
            else:
                top = stack.pop()
                stack.append(int(top / num))
            num = 0
            op = char

    return sum(stack)

Time Complexity: O(n), where n is the length of the string. We iterate through the string once.

Space Complexity: O(n) in the worst case, where n is the length of the string, due to the stack storing numbers.

Edge Cases

  • Empty string: Return 0.
  • String with only one number: Return the number.
  • String with leading/trailing spaces: Trim the spaces.
  • Division by zero: The problem statement guarantees a valid expression, so division by zero won't occur.
  • Negative intermediate results in division: Python's integer division truncates towards zero, as specified in the problem.

Summary

The optimal stack-based approach provides an efficient way to evaluate the expression in O(n) time and O(n) space. It handles operator precedence correctly and adheres to the problem's constraints.