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【「栈顶运算符」比「当前运算符」优先级高/同等,才进行运算】,消除递减区间,变成单调增。
- 遇到右括号,通过堆栈逆运算,得出结果,弹出右括号