Leetcode小白试炼(20201211 基本计算器)

一、题目

1. 题目描述

实现一个基本的计算器来计算一个简单的字符串表达式的值。
字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格 。
说明:
你可以假设所给定的表达式都是有效的。
请不要使用内置的库函数 eval。

2. 示例

示例1:
输入: “1 + 1”
输出: 2
示例2:
输入: " 2-1 + 2 "
输出: 3
实例3:
输入: “(1+(4+5+2)-3)+(6+8)”
输出: 23

3. 题目解析

非常复杂的一道实现计算器题,即需要考虑各种符号的优先级,又得考虑括号。

二、个人解法

1. 解法分析

使用和Leetcode小白试炼(20201210 基本计算器II)一样的方法来实现。不同的地方时对于括号的处理,遇到括号,递归调用本方法,把括号内的子串当做参数进行求和后得到一个整数。这个整数即可代表这个括号。
eg.
在这里插入图片描述

2. 失败反思

这是做的第一道困难题,思路明确,但是代码就是调试不出来。总结原因是关于递归不够熟悉,很多递归中的参数传递不知道如何处理。
失败原因1:把未遍历的子串作为参数传给递归函数,未处理好括号结束的标识符。
失败原因2:递归结束条件是遇到,未考虑递归结束后上一层递归如何继续处理。
调试修改的代码比较乱,就不再贴失败代码。

三、官网高星解法

1. 单栈递归法

(1)解法分析

整体思路和自己想的一样,值得学习的地方有两处:
a.递归传入原字符串,定义全局变量i,用于遍历字符串。这样不容易乱。(个人解法没想到用全局变量,导致每次找括号结束标识很难)
b.每次遍历到的字符先暂存到字符变量,防止中间触发递归函数后,遍历索引发生了变化,导致意外错误。(比如遍历到,这个时候进入递归,直至遇到跳出递归,这个时候遍历索引指向,正好符合本函数的跳出条件,导致函数直接终止。)

(2)代码

 public int i = 0;
    public int calculate(String s) {
  int result = 0;
        Stack<Integer> st = new Stack<>();
        int temp = 0;
        int kuohao = 0;
        char fu = '+';
        for (; i < s.length(); i++) {
//            这里要暂存一下当前遍历到的字符
            char ch = s.charAt(i);
            if (ch == '(') {
                i++;
//                求括号内的值
//                这里递归过后,i会变化,若前面没有ch保存当前字符,这里当前字符会发生变化,变成),导致后续判断一直是跳出递归。
//                暂存可以保证递归出来后,符号还是(之前的符号
                temp = calculate(s);
            }
            if (Character.isDigit(ch)) {
                temp = temp * 10 + (s.charAt(i) - '0');
            }
//             遇到非数字且非空格,这个时候分支处理,将之前的整数temp入栈,fu是上一个整数的符号
//            如果指针移动到最后一位,这个时候后面没有符号了,也需要分支处理
            if ((!Character.isDigit(ch) && ch != ' ') || i == s.length() - 1 || ch == ')') {
                switch (fu) {
                    case '+':
                        st.push(temp);
                        break;
                    case '-': 
                        st.push(-temp);
                        break;
                    case '*':
                        st.push(st.pop() * temp);
                        break;
                    case '/':
                        st.push(st.pop() / temp);
                        break;
                }
//                处理完上一个数,fu放当前符号
                fu = ch;
                temp = 0;
            }
            if (fu == ')' && i != s.length() - 1)
                break;
        }
        //        栈内所有元素相加即为最终结果
        while (!st.empty())
            result += st.pop();
        return result;
    }

(3)算法分析

虽然代码中使用了递归,但是总体来说只遍历了一次字符串。
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

(4)提交截图

在这里插入图片描述

2. 后缀表达式

(1)解法分析

只提到了这种思路,没有写具体解法,自己尝试写的,不够精简。。
由于括号会干扰我们的正常计算顺序,若将正常的中缀表达式转换为后缀表达式,可以避免括号带来的计算顺序问题。后缀表达式的计算参考:牛牛与后缀表达式
中缀转后缀步骤:

  • 实例化一个队列,一个符号栈,逐位遍历表达式。
  • 如果是数字,合并至变量temp
  • 如果上一位是数字,这一位非数字,则将temp入队(说明一一个操作数都放到了temp);若遍历至字符串末尾,且这一位是数字,则将temp入队
  • 如果是左括号,直接入符号栈
  • 如果是运算符,首先判断栈顶元素:若栈顶元素是左括号,该符号直接入栈;如果栈顶元素是符号,且优先级小于待处理符号,则直接入栈;若栈顶元素是符号,且优先级大于等于待处理符号,则先将栈顶元素出栈,入队,重复该步骤,直至待入栈符号能入栈。
  • 如果是右括号,则将符号栈元素依次出栈入队,直至遇到左括号,左括号出栈但不入队。
    后缀表达式求解步骤:
  • 实例化一个栈待用,遍历后缀表达式
  • 遇到数字入栈
  • 遇到符号,出栈俩元素进行操作,操作结果入栈
  • 遍历结束后,栈顶元素即为结果

(2)代码

public int calculate(String s) {
 int temp = 0;
        Stack<String> st1 = new Stack<>();
//        队列中存的就是后缀表达式
        Queue<String> st3 = new LinkedList<>();

        Stack<String> st2 = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
//            合并连续的数字位
            if (Character.isDigit(s.charAt(i))) {
                temp = temp * 10 + (s.charAt(i) - '0');
            }
            //两种情况整数入栈:1.上一位是数字,这一位不是数字,说明这个整数遍历结束;2.字符串最后一位是数字
            if((i>0&&Character.isDigit(s.charAt(i-1))&&!Character.isDigit(s.charAt(i)))||(i==s.length()-1&&Character.isDigit(s.charAt(i)))){
                st3.add(String.valueOf(temp));
                temp=0;
            }
//          处理符号
            if ((!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ') || i == s.length() - 1) {


                switch (s.charAt(i)) {
                    case '(':
                        st2.push(String.valueOf(s.charAt(i)));
                        break;
                    case ')':
                        while (!st2.peek().equals("(")) {
                            st3.add(st2.pop());
//                            st1.push(st2.pop());
                        }
                        st2.pop();
                        break;
                    case '+':
//                        st3.add(String.valueOf(temp));
//                        temp = 0;
//                        当栈不为空且当前优先级小于等于栈顶操作符优先级时,循环执行出栈并加入list操作。循环执行完再将当前操作符入栈
                        while (!st2.empty() && (st2.peek().equals("*") || st2.peek().equals("/") || st2.peek().equals("-") || st2.peek().equals("+"))) {
//                            st1.push(st2.pop());
                            st3.add(st2.pop());
                        }
                        st2.push("+");
                        break;
                    case '-':
//                        st3.add(String.valueOf(temp));
//                        temp = 0;
                        while (!st2.empty() && (st2.peek().equals("*") || st2.peek().equals("/") || st2.peek().equals("+") || st2.peek().equals("-"))) {
//                            st1.push(st2.pop());
                            st3.add(st2.pop());
                        }
                        st2.push("-");
                        break;
                    case '*':
//                        st3.add(String.valueOf(temp));
//                        temp = 0;
                        while (!st2.empty() && (st2.peek().equals("/") || st2.peek().equals("*"))) {
//                            st1.push(st2.pop());
                            st3.add(st2.pop());
                        }
                        st2.push("*");
                        break;
                    case '/':
//                        st3.add(String.valueOf(temp));
//                        temp = 0;
                        while (!st2.empty() && (st2.peek().equals("/") || st2.peek().equals("*"))) {
//                            st1.push(st2.pop());
                            st3.add(st2.pop());
                        }
                        st2.push("/");
                        break;
                }


            }
        }
        while (!st2.empty()) {
//            st1.push(st2.pop());
            st3.add(st2.pop());
        }
//至此,中缀表达式转换后缀表达式完毕,st3为后缀表达式所在队列
        Stack<Integer> stt = new Stack<>();
        int result = 0;
        while (!st3.isEmpty()) {
            switch (st3.peek()) {
                case "+":
                    st3.poll();
                    stt.push(stt.pop() + stt.pop());
                    break;
                case "-":
                    st3.poll();
                    int tt = stt.pop();
                    stt.push(stt.pop() - tt);
                    break;
                case "*":
                    st3.poll();
                    stt.push(stt.pop() * stt.pop());
                    break;
                case "/":
                    st3.poll();
                    tt = stt.pop();
                    stt.push(stt.pop() / tt);
                    break;
                default:
                    stt.push(Integer.parseInt(st3.poll()));
            }
        }
        if(stt.empty()){
            return 0;
        }
        return stt.pop();
    }

(3)算法分析

用了两个栈一个队列,遍历了一次字符串,一次队列。
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

(4)提交截图

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值