是一道笔试题,这道题居然被分为简单题。我是觉得真不简单,还是能力太差。
看了解析后,知道要用几个关键点:
- 用到栈,先进后出
- 用到递归,遇到做括号就递归
- 减法当成加负数处理
- 遇到乘除出栈计算后再入栈,栈中只有加法
但是看完别人的分析,知道了要用什么东西,依然觉得很难,太多细节要处理了,花了整整一晚上才写出来,也不知道有没有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;
}
}
说实在的,即使我加了很多注释,但是别人看起来估计还是很费劲,最好的办法还是按照思路自己写一遍。两个小时内能写出完整的就很不错了。所以笔试题分类到简单题我不太理解。
代码还有可改进地方,但是这代码折腾我辛苦了,懒得优化了。