是一道笔试题,这道题居然被分为简单题。我是觉得真不简单,还是能力太差。
看了解析后,知道要用几个关键点:

  1. 用到栈,先进后出
  2. 用到递归,遇到做括号就递归
  3. 减法当成加负数处理
  4. 遇到乘除出栈计算后再入栈,栈中只有加法

但是看完别人的分析,知道了要用什么东西,依然觉得很难,太多细节要处理了,花了整整一晚上才写出来,也不知道有没有bug。

要求

运算包括加减乘除和括号“()”、"[]"和“{}”(这里括号其实没有区别的,只是方便我们看而已)。还有负数。要求计算值。这个就不属于简单四则运算了,算复杂的了。当然,这里为了简化运算,保证所有数字都为整数,所以我全部用的int类型。

实现

import java.util.Arrays;
import java.util.Stack;

public class Main {

    public static void main(String[] args) {
        char[] charArr = "3+2*{1+2*[-4/(8-6)+7]}".toCharArray();
//        char[] charArr = {'(','-','1', '2', '/', '-','2',')','-','2'}; //
//        char[] charArr = {'-','1', '2', '/', '-','2', '/', '-','2'};//
        System.out.println(cal(charArr)[0]);


    }
    /**
     * @author lsjweiyi
     * @date 2021/8/19
     * 计算的主要实现方法
     */
    public static int[] cal(char[] charArr) {
        int sum = 0; // 和
        int len = charArr.length;
        Stack<Integer> stack = new Stack<>(); // 栈
        int i = 0; // 因为要利用i来计算以此递归处理了多少个字符,所以放到外面来定义
        for (; i < charArr.length; i++) {
            // result记录两个数据,result[0]是本次递归处理的数的和。result[1]是本次递归处理了多少个字符
            int[] result;
            if (charArr[i] == ')' || charArr[i] == ']' || charArr[i] == '}') { // 遇到右括号说明本次递归结束
                break;
            } else if (charArr[i] == '(' || charArr[i] == '[' || charArr[i] == '{') {//遇到左括号就进入新的递归
                // 递归,入参是当前字符+1到末尾。因为并不知道右括号在什么位置,所以直接取到末尾
                result = cal(Arrays.copyOfRange(charArr, i + 1, len));
                stack.push(result[0]);// 讲一组括号内计算的值入栈
                i += result[1]; // 递归是处理了一批字符的,所以下次处理是当前位置加上递归处理的字符长度,继续处理
            } else if (charArr[i] >= '0' && charArr[i] <= '9') { //遇到数字的处理
                // 因为字符数组存储的是单个数字,而真正的数字肯定有好几位数,所以需要讲连续的几个数字字符合并成一个整数
                result = findNum(i, charArr);
                stack.push(result[0]); //取到数字先入栈
                i += result[1] - 1;
            } else {
                // 获取运算符后面的数
                result = findNum(i + 1, charArr);
                int tempI=i; // 后面的i可能会变动,所以要记录下当前值
                if (result[1] == 0) { // 若寻找数字时一个字符都没运算,则可以确定后面后面跟的是括号
                    i++; //跳过括号
                    result = cal(Arrays.copyOfRange(charArr, i + 1, len));//是括号则递归求值
                }
                if (charArr[tempI] == '*') {
                    int number1 = stack.pop(); // 遇到乘除要先出栈,与后面的数字计算完再次入栈,因为栈中只存储加法运算
                    int number2 = result[0];
                    stack.push(number1 * number2);// 计算完在入栈
                } else if (charArr[tempI] == '/') {
                    int number1 = stack.pop();
                    int number2 = result[0];
                    stack.push(number1 / number2);
                } else if (charArr[tempI] == '-') {
                    stack.push(-result[0]); // 减法相当于加负数
                } else if (charArr[tempI] == '+') {
                    stack.push(result[0]);
                }
                i += result[1]; // 确定当前运算到什么位置了
            }
        }
        //将栈中数字求和
        while (!stack.empty()) {
            sum += stack.pop();
        }
        return new int[]{sum, i + 1};// 第一个数是当前栈中的和,第二个数字是本次递归处理的字符数
    }
    /**
     * @author lsjweiyi
     * @date 2021/8/19
     * 整合连续几个字符数字为一个完整的整数
     */
    public static int[] findNum(int index, char[] charArr) {
        int i = index;
        int fuHaoWei = 1; // 可能会遇到负数,所以最终要乘以符号位,默认为1,即正整数
        // 判断是否为负数
        if (charArr[i] == '-') {
            fuHaoWei *= -1; //若为负数则符号位=-1
            i++; // 此时处理了一个“-”,也要记得加1
        }
        // 计算连续数字的长度,找到这个范围
        for (; i < charArr.length; i++) {
            // 当遇到超出数字字符范围的字符,即连续数字结束
            if (charArr[i] < '0' || charArr[i] > '9') {
                break;
            }
        }
        if (fuHaoWei == -1) {
            //输出还是上面那个result数组。负数因为多了个“-”开头,所以index + 1
            return new int[]{fuHaoWei * pinShuzi(Arrays.copyOfRange(charArr, index + 1, i)), i - index};
        } else {
            return new int[]{pinShuzi(Arrays.copyOfRange(charArr, index, i)), i - index};
        }
    }
    /**
     * @author lsjweiyi
     * @date 2021/8/19
     * 将连续数字拼接成一个真正的整数,这里用位运算可能更优雅
     */
    public static int pinShuzi(char[] charArr) {
        int sum = 0;
        int radix = 1; // 从个位数算起,默认为1
        for (int i = charArr.length - 1; i >= 0; i--) {
            sum += Integer.parseInt(String.valueOf(charArr[i])) * radix;
            radix *= 10; // 每前进一位,要乘10.
        }
        return sum;
    }
}

说实在的,即使我加了很多注释,但是别人看起来估计还是很费劲,最好的办法还是按照思路自己写一遍。两个小时内能写出完整的就很不错了。所以笔试题分类到简单题我不太理解。

代码还有可改进地方,但是这代码折腾我辛苦了,懒得优化了。