实现一个计算器,能够计算一个字符串表达式的值,如"3-2*(1-3)"=?
思路:
假设我们的算式是类似'3-1+1*3/4'这类运算符优先级从左到右非递减的。那么我们就可以从右到左计算出该表达式的值。这给予我们一个启发,就是我们维护一个运算符栈opts,栈中存放的每个运算符非递减。同时维护一个数字栈nums来存放数字,用'3-1+1*3/4'来举例就是opts=[-,+,*,/],nums=[3,1,3,4].这样我们在整理完两个栈之后从nums末尾弹出两个元素,opts中弹出一个操作符计算当前计算值,并将结果重新入nums栈。在整理栈的过程中有以下几种情况
- 当前元素是'(',这种情况不用计算,直接放入opts栈中即可
- 当前元素是')',这时已经找到了一对括号,因此需要不断从opts栈中弹出操作符,直到将与之匹配的 '('弹出为止
- 当前元素是数字,这时我们需要往后截取出完整的数字放入nums栈中
- 当前元素是运算符,因为我们要维护的是一个优先级单调栈,因此如果当前运算符优先级小于栈顶运算符优先级,那么就需要处理一下栈顶的运算符,把栈顶的运算符计算后消耗掉。这样才能保持我们的opts是单调的。
Tips:
- 注意有可能第一个数字是负数,如'-3-3'的情况。这时为了运算过程中不出错我们需要事先在nums中塞入一个0,也就是变为0-3-3的情况
- 还有另外一种情况是()括号中的第一个数字是负数,这时也会造成nums和opts数目不对等导致出错,因此我们需要将'(-'替换为'(0-)'。如'2-(-3+2)'替换为'2-(0-3+2)'。这样才能保证两个栈数目对等
代码实现
class Solution {
private HashMap<Character, Integer> operatorPriority = new HashMap() {{
put('+', 0);
put('-', 0);
put('*', 1);
put('/', 1);
}};
private Deque<Integer> nums = new ArrayDeque() {{
add(0);
}};
private Deque<Character> opts = new ArrayDeque();
public int calculate(String s) {
s = s.replaceAll(" ", "");
s = s.replaceAll("\\(-", "(0-");
char[] sArr = s.toCharArray();
for (int i = 0; i < sArr.length; i++) {
char c = sArr[i];
if (c == '(') {
opts.add(c);
} else if (c == ')') {
while (!opts.isEmpty()){
if(opts.peekLast()!='('){
calc();
}else {
opts.pollLast();
break;
}
}
} else if (Character.isDigit(c)) {
int ans=0;
//截取完整的数字
while (i<sArr.length&&Character.isDigit(sArr[i])){
ans=ans*10+(sArr[i++]-'0');
}
i--;
nums.add(ans);
} else {
// 运算符,维护一个优先级单调栈,这样在最后的时候从后往前计算所有运算符就好
while (!opts.isEmpty()&&opts.peekLast()!='('){
char curOpt=opts.peekLast();
if(operatorPriority.get(curOpt)>=operatorPriority.get(c)){
calc();
}else{
break;
}
}
opts.add(c);
}
}
while (!opts.isEmpty()){
calc();
}
return nums.peekLast();
}
//将运算符栈中弹出一个运算符和数字栈中弹出两个数字进行计算
private void calc() {
if (nums.size() < 2 || opts.isEmpty()) return;
int right = nums.pollLast();
int left = nums.pollLast();
char opt = opts.pollLast();
int ans = 0;
if (opt == '+') {
ans = left + right;
} else if (opt == '-') {
ans = left - right;
} else if (opt == '*') {
ans = left * right;
} else {
ans = left / right;
}
nums.add(ans);
}
}