Skip to content

Latest commit

 

History

History
294 lines (258 loc) · 9.39 KB

227.basic-calculator-ii.md

File metadata and controls

294 lines (258 loc) · 9.39 KB

This is a series of calculator problems:

Before solving this problem, we can break down the problems into several parts:

Parse Number

val s = "1234"
var num = 0
for (c in s) {
    num = num * 10 + (c - '0')
}
return num

Simple Addition and Subtraction

For s = 300 + 10 - 5, how to calculate the addition and subtraction? We can break down the steps:

  1. We can trim the space and append a + at the beginning of the string: +300+10-5
  2. The idea to solve this problem is that we treat every item as [operator and number]. We parse the number and operator pairs: +300, +10, -5, then push into stack.
  3. We sum up all the elements in the stack: 300 + 10 - 5 = 305

There is a special case: The last number, we also have to push it into the stack.

fun calculate(s: String): Int {
    val stack = Stack<Int>()
    var num = 0
    var sign = '+' // We initialize the sign as `+` at the beginning of the string
    for (i in 0 until s.length) {
        val c = s[i]
        if (c.isDigit()) {
            num = num * 10 + (c - '0')
        }

        // If the character is a operator or the last number, see below explanation:
        // +300+ ...
        //     c        We should push +300 into the stack
        //          -5
        //           c  We should push -5 into the stack for the         
        if ((!c.isDigit() && c != ' ') || i == s.lenght - 1) {
            // We push the operator and number into the stack when we encounter the next operator (current index i)
            // This is the previous sign and the number next to it
            when (sign) {
                '+' -> stack.push(num)
                '-' -> stack.push(-num)
            }   
            // Update to the current sign
            sign = c
            // Reset the number for the next number
            num = 0
        }
    }
    // Stack: [+0, +300, +10, -5]
    var sum = 0
    while (stack.isNotEmpty()) {
        sum += stack.pop()
    }
    return sum
}

Let's explain the code:

...
// 300 + 10 - 5
//          ^ i is here, next to `+10`
if (!c.isDigit() && c != ' ' || i == s.lenght - 1) {
    // Push `+10` into stack
    when (sign) {               // sign = `+` before 10
        '+' -> stack.push(num)  // num = 10
        '-' -> stack.push(-num)
    }   
    // Update to the current sign
    sign = c  // sign = `-` before 5
    // Reset the number for the next number
    num = 0
}
...

```js
      i
_ 300 + 10 - 5
|   |
|   num = 300
sign = + // We initialize the sign as `+` at the beginning of the string

// Next operator
         i
300 + 10 - 5
    |  | 
    |  num = 10
    sign = +

Original Problem

After understanding the above concepts, now we can solve the original problem. The difference between this problem and the previous one is that we have to calculate the multiplication and division first, then sum up all the elements in the stack.

We still trim the space and append a "+" at the beginning of the string, and parse the string as [operator and number], for example, 3 - 2 * 5 / 2 + 7 will be +3, -2, *5, /2, +7. Then we calculate all the results of * and / first which has high priority than + and -, and then every item will be a plus or minus numbers that have the same priority, we can sum them up to get the result.

Based on this idea, w can iterate the string and parse the number after the operator:

  • If the operator is + or -, we push the number into the stack.
  • If the operator is * or /, because we have to calculate the result immediately which * and /, we pop the last number from the stack, calculate the result, and push it back to the stack. This is how we calculate the multiplication and division first.
s = "3 - 2 * 5 / 2 + 7"

// Trim the space and append a "+" at the beginning of the string
s = "+3-2*5/2+7"

s = "+3-2*5/2+7"
     ^^ // Push +3 into stack
stack = [+0, +3] 

s = "+3-2*5/2+7"
       ^^ // Push -2 into stack
stack = [+0, +3, -2] 

s = "+3-2*5/2+7"
         ^^ 
stack = [+0, +3, -10] // We pop -2 and calculate -2 * 5 = -10, then push -10 back to stack

s = "+3-2*5/2+7"
           ^^ 
stack = [+0 +3, -5] // We pop -10 and calculate -10 / 2 = -5, then push -5 back to stack

s = "+3-2*5/2+7"
             ^^ // Push +7 into stack
stack = [+0, +3, -5, +7]

// Sum up all the elements in stack: 3 - 5 + 7 = 5
return 5 
fun calculate(s: String): Int {
    val stack = Stack<Int>()
    var num = 0
    var operation: Char = '+'
    val operationSet = hashSetOf('+', '-', '*', '/')
    for (i in 0 until s.length) {
        val c = s[i]
        if (c.isDigit()) {
            // Wrong to use `c.toInt()` is the ASCII code of the character, not the number
            num = num * 10 + (c - '0')
        }
        if (operationSet.contains(c) || i == s.length - 1) {
            when (operation) {
                '+' -> {
                    stack.push(num)
                }
                '-' -> {
                    stack.push(-num)
                }
                '*' -> {
                    stack.push(stack.pop() * num)
                }
                '/' -> {
                    stack.push(stack.pop() / num)
                }
            }
            operation = c
            num = 0
        }
    }
    var sum = 0
    while (stack.isNotEmpty()) {
        sum += stack.pop()
    }
    return sum
}

How does this code work when we have - at the beginning of the string?

s = "-5 + 3"

num = 0
operation = '+'
stack = []

c = '-' // Is operation
stack = [+0] // We push current operation '+' and num '0' into the stack
operation = '-'
num = 0

c = '5' // Is digit
num = 5

c = '+' // Is operation
stack = [+0, -5] // We push the previous operation '-' and num '5' into the stack

c = '3' // Last index
stach = [+0, -5, +3] // We push the current operation '+' and num '3' into the stack

Or equivalently, we can use the following code to calculate the result:

  1. We do really append + at the beginning of the string and trim the space.
  2. We iterate the string and parse the [operator and number], then push them into the stack. (For * and /, we have to calculate the result immediately)
  3. We sum up all the elements in the stack.
fun calculate(str: String): Int {
    val s = "+" + str.replace("\\s+".toRegex(), "")
    val stack = Stack<Int>()
    var i = 0
    while (i < s.length) {
        val c = s[i]
        if (c == '+' || c == '-') {
            // Parse the number after the operator
            var num = 0
            while (i + 1 < s.length && s[i + 1].isDigit()) {
                i++
                num = num * 10 + (s[i] - '0')
            }
            if (c == '+') {
                stack.push(num)
            } else {
                stack.push(-num)
            }
        } else if (c == '*' || c == '/') {
            // Parse the number after the operator
            var num = 0
            while (i + 1 < s.length && s[i + 1].isDigit()) {
                i++
                num = num * 10 + (s[i] - '0')
            }
            val last = stack.pop()
            if (c == '*') {
                stack.push(last * num)
            } else {
                stack.push(last / num)
            }
        }
        i++
    }
    // There is no `*` and `/` in the stack, we can sum up all the elements in the stack
    var sum = 0
    while (stack.isNotEmpty()) {
        sum += stack.pop()
    }
    return sum
}

Or we can split by operator and number to get the numbers and operators, then we can the apply the same idea to calculate the result.

This approach may not work correctly if we have leading negative numbers, for example, -5 + 3. We could append a leading "0" to the string to avoid this issue.

s = "3 - 2 * 5 / 2 + 7"
operators = s.split(number) = ["", "-", "*", "/", "+", ""] // Note that the first and last element are empty
numbers = s.split(operator) = ["3", "2", "5", "2", "7"]

// We hope to calculate in the following ways:
  0    1    2    3    4 
[ "" ,"-", "*", "/", "+", ""]
["3", "2", "5", "2", "7"]
       i ->
  3,  -2,  *5,  /2,  +7

// Then we can apply the same idea to calculate the result
fun calculate(str: String): Int {
    val s = str.replace("\\s+".toRegex(), "")
    val operators = s.split("\\d+".toRegex())
    val nums = s.split("[+\\-*/]".toRegex()).map { it.toInt() }
    val stack = Stack<Int>()
    stack.push(nums[0].toInt())
    for (i in 1 until nums.size) {
        val num = nums[i]
        val op = operators[i]
        if (op == "+") {
            stack.push(num)
        } else if (op == "-") {
            stack.push(-num)
        } else if (op == "*") {
            stack.push(stack.pop() * num)
        } else if (op == "/") {
            stack.push(stack.pop() / num)
        }
    }
    var sum = 0
    while (stack.isNotEmpty()) sum += stack.pop()
    return sum
}

Reference

https://leetcode.cn/problems/basic-calculator-ii/solutions/91271/chai-jie-fu-za-wen-ti-shi-xian-yi-ge-wan-zheng-ji-/ (There contains the solution to 772. Basic Calculator III)