Leetcode224 基本计算器 双栈,或转后缀表达式求值

224. 基本计算器

难度困难750

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。

 

示例 1:

输入:s = "1 + 1"
输出:2

示例 2:

输入:s = " 2-1 + 2 "
输出:3

示例 3:

输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23

 

提示:

  • 1 <= s.length <= 3 * 105
  • s 由数字、'+''-''('')'、和 ' ' 组成
  • s 表示一个有效的表达式
  • '+' 不能用作一元运算(例如, "+1" 和 "+(2 + 3)" 无效)
  • '-' 可以用作一元运算(即 "-1" 和 "-(2 + 3)" 是有效的)
  • 输入中不存在两个连续的操作符
  • 每个数字和运行的计算将适合于一个有符号的 32位 整数

这道题可以用两种做法:

第一种朴素的做法是:

将字符串转化为 后缀表达式, 然后利用栈进行求值。

这种做法的注意点:

1.首先把字符串中间多余的空格得处理了。String.replaceAll处理

2.可能会遇到负数,以及 类似于 -(2 + 3)这种情况 或者 a + (-b)这种情况 或者 a - ( + b + b)  具体处理做法加0 将离奇古怪的表达都转化为正常表达,将对负数的运算转化为对 0 - 正数的运算。 像      -(2+3)的话 得要在前面+0 变成 0-(2+3)  a +(-b)变为 a + (0 - b)等。

对字符串处理完,就要将字符串转为后缀表达式,具体用辅助栈进行。辅助栈用来接受操作符。在转换的时候要用到操作符之间的优先级,所以 为了快速查找优先级,在map中定义好 操作数的优先级。

这里用ArrayList 当作结果集。

具体转后缀表达式的做法:

1. 如果当前遇到操作数,直接入结果集。

2.如果遇到 "("直接入栈。

3.如果遇到 "+","-","*","/"中的任何一个(*,/ 的优先级大于加减, 乘除优先级相同,加减优先级相同)首先检查栈顶元素优先级是不是大于等于 当前操作符优先级,如果大于等于当前操作数则出栈并且进入结果集,直到栈空或遇到小于此优先级的操作符或遇到“(”,然后加入当前操作符 , 左括号其实是优先级最大的运算符,但是它是个意外,规定不准弹出。

4.如果遇到“)”,则持续弹出栈中操作符,入结果集,知道遇到左括号,然后把左括号弹出丢掉,注意后缀表达式不允许有括号,所以括号要丢掉。

上述操作是中缀转后缀表达式的做法。

然后利用栈进行后缀表达式求职,具体代码如下:

class Solution {
    public int calculate(String s) {
        s = s.replaceAll(" ", "");
        StringBuilder sb = new StringBuilder();
        if(s.charAt(0) == '-'){
            sb.append(0);
        }
        sb.append(s.charAt(0));
        for(int i = 1; i < s.length(); i++){
            char pre = s.charAt(i - 1);
            if((pre == '(' || pre == '+' || pre == '-') && (s.charAt(i) == '+' || s.charAt(i) == '-')){
                sb.append(0);
            }
            sb.append(s.charAt(i));
        }
        ArrayList<String> list = conver(sb.toString());
        Stack<Integer> stack = new Stack();
        for(String e : list){
            if(e.equals("+") || e.equals("-")){
                int a = stack.pop();
                int b = stack.pop();
                int result = 0;
                if(e.equals("+")) result = a + b;
                else result = b - a;
                stack.push(result);
            }else{
                stack.push(Integer.parseInt(e));
            }
        }
        return stack.peek();
    }

    public ArrayList<String> conver(String s){
        Stack<String> stack = new Stack();

        ArrayList<String> list = new ArrayList();
        for(int i = 0; i < s.length(); i++){
            char cur = s.charAt(i);
            if(cur >= '0' && cur <= '9'){
                int end = i + 1;
                String num = "" + s.charAt(i);
                while(end < s.length() && s.charAt(end) >= '0' && s.charAt(end) <= '9'){
                    num = num + s.charAt(end); 
                    end++;
                }
                i = end - 1;
                list.add(num);
            }else if(cur == '('){
                stack.push(cur+"");
            }else if(cur == ')'){
                while(!stack.peek().equals("(")){
                    list.add(stack.pop());
                }
                stack.pop(); 
            }else{
                while(!stack.isEmpty() && !stack.peek().equals("(")){
                    list.add(stack.pop());
                }
                stack.push(cur+"");
            }
        }
        while(!stack.isEmpty()){
            list.add(stack.pop());
        }
        return list;
    }
}

这种操作比较简单:比较容易理解。

方法2:利用双栈,进行中缀表达式求值。

一个栈入操作数,一个栈入操作符。

1. 数据处理, 去掉空格。

2.为了防止出现第一个是负号 即 -(2 + 3)在操作数栈先入一个0.

3.然后一边遍历,一边求值:

如果当前 是 左括号,直接入栈,如果是数字,则一直读到不是数字的地方停止,将数入操作数栈。

如果是操作符,则先将这个操作符前面 前面的数都算好(算到直到栈为空,或者遇到左括号)并结果入栈,然后判断操作符左边 是不是左括号如果是则在操作数栈入0(防止(-a)(+b)这种情况发生,操作数栈除了算出来的负数,不加任何原字符串的负数。

如果是右括号,则不断出操作数栈和操作符栈,进行计算并且结果入栈。

循环结束后,还要将操作符栈和操作数出栈计算结果。操作数栈道最后一个就是所求答案。

代码如下:

class Solution {
    public int calculate(String s) {
        // 存放所有的数字
        Deque<Integer> nums = new ArrayDeque<>();
        // 为了防止第一个数为负数,先往 nums 加个 0
        nums.addLast(0);
        // 将所有的空格去掉
        s = s.replaceAll(" ", "");
        // 存放所有的操作,包括 +/-
        Deque<Character> ops = new ArrayDeque<>();
        int n = s.length();
        char[] cs = s.toCharArray();
        for (int i = 0; i < n; i++) {
            char c = cs[i];
            if (c == '(') {
                ops.addLast(c);
            } else if (c == ')') {
                // 计算到最近一个左括号为止
                while (!ops.isEmpty()) {
                    char op = ops.peekLast();
                    if (op != '(') {
                        calc(nums, ops);
                    } else {
                        ops.pollLast();
                        break;
                    }
                }
            } else {
                if (isNum(c)) {
                    int u = 0;
                    int j = i;
                    // 将从 i 位置开始后面的连续数字整体取出,加入 nums
                    while (j < n && isNum(cs[j])) u = u * 10 + (int)(cs[j++] - '0');
                    nums.addLast(u);
                    i = j - 1;
                } else {
                    if (i > 0 && (cs[i - 1] == '(' || cs[i - 1] == '+' || cs[i - 1] == '-')) {
                        nums.addLast(0);
                    }
                    // 有一个新操作要入栈时,先把栈内可以算的都算了
                    while (!ops.isEmpty() && ops.peekLast() != '(') calc(nums, ops);
                    ops.addLast(c);
                }
            }
        }
        while (!ops.isEmpty()) calc(nums, ops);
        return nums.peekLast();
    }
    void calc(Deque<Integer> nums, Deque<Character> ops) {
        if (nums.isEmpty() || nums.size() < 2) return;
        if (ops.isEmpty()) return;
        int b = nums.pollLast(), a = nums.pollLast();
        char op = ops.pollLast();
        nums.addLast(op == '+' ? a + b : a - b);
    }
    boolean isNum(char c) {
        return Character.isDigit(c);
    }
}


作者:AC_OIer
链接:https://leetcode-cn.com/problems/basic-calculator/solution/shuang-zhan-jie-jue-tong-yong-biao-da-sh-olym/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值