力扣 772 计算器3 双栈解法

class Solution {

    public int calculate(String s) {
        /*
         * 创建两个栈 numStack 和 opStack:
         * numStack : 存放所有的数字
         * opStack :存放所有的数字以外的操作,+/- 也看做是一种操作
         */
        Deque<Integer> numStack = new ArrayDeque<>();
        Deque<Character> opStack = new ArrayDeque<>();

        // 这里的优先级划分按照「数学」进行划分即可
        // 使用 map 维护一个运算符优先级
        Map<Character, Integer> map = new HashMap<>();
        map.put('+', 1);
        map.put('-', 1);
        map.put('*', 2);
        map.put('/', 2);
        map.put('%', 2);
        map.put('^', 3);

        // 去掉字符串的所有空格
        s = s.replaceAll(" ", "");

        // 为了防止第一个数为负数,先往 nums 加个 0
        // 例如 -1 + 2 情况
        numStack.push(0);

        /*
         * 遍历字符串(分情况讨论):
         * 1. 如果取到 ( , 直接加入 opStack 中,等待与之匹配的 )
         * 2. 如果取到 ) , 使用现有的 numStack 和 opStack 进行计算,
         * 再弹出左括号(也就是和当前右括号相匹配的左括号)
         * 3. 如果取到一个数字, 从当前位置开始继续往后取, 将整一个连续数字整体取出, 加入 numStack
         * 4. 如果取到运算符, 说明之前已经是一个完整的计算表达式, 如果有操作符而且这个操作符不是左括号,
         * 而且只有「栈顶运算符」比「当前运算符」优先级高/同等,才可以使用现有的 numStack 和 opStack 进行计算,
         * 计算结果放到 numStack, 相到于得到了当前运算符的左操作数,最后放入当前运算符
         */
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                opStack.push(c);
            } else if (c == ')') {
                /*
                 * 如果运算符栈顶不是 (,说明括号内有运算,直接进行计算,再弹出左括号
                 * 否则说明括号内没有运算,直接弹出左括号, 例如(1)
                 * 因为混合运算,括号前可能有多个运算符可以计算,例如(1+2*3),所以需要使用while循环把可以计算的先算完
                 * 重复计算到最近一个左括号为止
                 */
                while (!opStack.isEmpty()) {
                    if (opStack.peek() != '(') {
                        cal(numStack, opStack);
                    } else {
                        opStack.pop();
                        break;
                    }
                }
            } else if (Character.isDigit(c)) {
                // 将从 i 位置开始后面的连续数字整体取出,加入 numStack
                int number = 0;
                int j = i;
                while (j < s.length() && Character.isDigit(s.charAt(j))) {
                    number = number * 10 + (s.charAt(j) - '0');
                    j++;
                }
                numStack.push(number);
                // 此时的j位置是第一个不为数字的位置,将遍历的位置调整到j-1
                i = j - 1;
            } else {
                // 为防止 () 内出现的首个字符为运算符,将 (- 替换为 (0-,(+ 替换为 (0+
                if (i > 0 && s.charAt(i - 1) == '(')
                    numStack.push(0);
                /*
                 * 使用现有的 numStack 和 opStack 计算表达式
                 * 运算符栈不为空而且前一个是运算符不是括号才可以计算,例如(3+2+1)在读取到第一个+时不能计算
                 * 因为是混合运算,当一个新的运算符要入栈时,之前的表达式可能有多个运算符可以计算
                 * 需要把栈内的可以计算的都计算完,例如1*2-3/4+2在读到加号时,不仅前面的/要计算,前面的-也需要计算
                 * 所以需要使用while循环把能计算的都两两计算完
                 */
                while (!opStack.isEmpty() && opStack.peek() != '(') {
                    // 只有满足「栈顶运算符」比「当前运算符」优先级高/同等,才进行运算
                    if (map.get(opStack.peek()) >= map.get(c)) {
                        cal(numStack, opStack);
                    } else {
                        break;
                    }
                }
                // 操作符入栈
                opStack.push(c);
            }
        }
        // 如果最后一个是数字,读取完之后没有进行计算
        // 此时要把剩余的计算完, 可能有多个都没有计算
        // 例如3+2*2, 所以要用while循环
        while (!opStack.isEmpty())
            cal(numStack, opStack);
        return numStack.peek();
    }

    // 使用现有的 num栈 和 op栈 进行表达式计算
    public void cal(Deque<Integer> numStack, Deque<Character> opStack) {
        // 取出两个数和一个运算符进行计算
        int num2 = numStack.pop(), num1 = numStack.pop();
        char op = opStack.pop();
        if (op == '+')
            numStack.push(num1 + num2);
        else if (op == '-')
            numStack.push(num1 - num2);
        else if (op == '*')
            numStack.push(num1 * num2);
        else if (op == '/')
            numStack.push(num1 / num2);
        else if (op == '%')
            numStack.push(num1 % num2);
        else if (op == '^')
            numStack.push((int) Math.pow(num1, num2));
    }
}

  • 由于存在括号和优先级,通过括号在栈内分层
  • 如果是操作符,通过不断while【「栈顶运算符」比「当前运算符」优先级高/同等,才进行运算】,消除递减区间,变成单调增。
  • 遇到右括号,通过堆栈逆运算,得出结果,弹出右括号
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值