1. 题目描述
请写一个整数计算器,支持加减乘三种运算和括号。
数据范围:0≤ |s| ≤100,保证计算结果始终在整型范围内
要求:空间复杂度: O(n),时间复杂度 O(n)
示例1
输入:"(2*(3-4))*5"
返回值:-10
示例2
输入:"3+2*3*4-1"
返回值:26
2. 解题思路
对于任何表达式求值的问题, 我们都可以使用双栈来解决 !!
- 一个栈用来存放所有数字; nums
- 一个栈用来存放出数字以外的运算符以及括号. opt
具体实现思路:
1. 碰到 '(', 直接加入 opt 栈, 等待与之匹配的 ')'.
2. 碰到 ')', 就可以按照规则进行两个相匹配括号之间的一些 +,-,* 的运算了.
3. 遇到数字需要将数字的整体取出, 例如 35, 它不止占有一个字符, 所以可以使用固定套路:
sum = sum * 10 + ch - '0' 来将一整个数字取出, 然后加入 nums 栈.
4. 遇到 +, -, * 运算符, 需要先将 opt 栈内能够运算的操作都进行运算完之后, 再将该运算符加入 opt 栈中. 此处要遵循一个规则: 满足栈内运算符优先级高于等待入栈的运算符的优先级才可以先将栈内运算符进行运算, 否则不运算, 直接将等待入栈的运算符进行入栈操作.
例如: 栈内的运算符只有一个 + 号, nums 栈中有 2, 3 两个数字, 而等待入栈的运算符是 * , 此时 * 的优先级高于 + , 所以不进行 2 + 3 的运算, 而是直接将 * 入 opt 栈; 如果栈内的运算符是 * , 而等待入栈的运算符是 + , 那么则需要先将 2 * 3 运算完成之后, 将结果入 nums 栈后, + 才可以入 opt 栈.
实现上的细节:
1. 输入的字符串中可能带有空格, 遇到空格, 就使用空串替换掉. 虽然示例上没有体现出来, 但是我们处理了肯定不会影响通过这道题, 如果不处理那还是有可能通不过这道题的.
2. 如果出现第一个数字是带正负符号的情况, 或者出现运算符后面的数字是一个带正负符号的情况, 这时候需要特殊处理:
>>> 例如 (-4 - 2), -4 + 2 , 4 - +2 等特殊情况, 我们可以在第一个括号后面加 0, 或者在第一个 - 或者 + 运算符后面添 0, 这样做预处理就不需要进行边界判断了.
int num1 = 4- +2; // 情况一: 4 - +2 -> 4 - 0 + 2
int num2 = (-4 - 2); // 情况二: (-4 - 2) - > (0 - 4 - 2)
int num3 = -4 + 2; // 情况三: -4 + 2 - > 0 - 4 + 2
>>> 对于 4 * -2 , 4 * + 2 这种情况在我们的认知里是不太合理的, 在上小学的时候, 老师就会告诉我们这种情况, 要对数字进行加括号处理, 如 : 4 * (-2), 4 * (+2), 而此题认为这种情况是不合法的, 所以没有给出这样的测试用例, 所以可以不做处理. (如果非要处理, 那么可以在对狮式子进行运算前先对字符串中 * 后面的运算符以及数字一整个都用括号包裹起来.)
3. 代码实现
public class Demo1 {
// 维护运算符优先级
Map<Character, Integer> map = new HashMap<Character, Integer>() {{
put('+', 0);
put('-', 0);
put('*', 1);
}};
public int solve (String s) {
s = s.replaceAll(" ", "");
char[] array = s.toCharArray();
int n = array.length;
// 用于存放操作数
Stack<Integer> nums = new Stack<>();
nums.push(0); // 防止第一个数是负数的情况
// 用于存放运算符
Stack<Character> opt = new Stack<>();
for(int i = 0; i < n; i++) {
if(array[i] == '(') {
opt.push(array[i]);
} else if(array[i] == ')') {
// 遇到 ) , 就将与之匹配的括号中间的部分计算完
while(!opt.isEmpty()) {
if(opt.peek() != '(') {
calc(nums, opt);
} else {
opt.pop(); // 左括号出栈
break;
}
}
} else {
if(isNumber(array[i])) {
int sum = 0;
int j = i;
while(j < n && isNumber(array[j])) {
sum = sum * 10 + (array[j] - '0');
j++;
}
nums.push(sum);
i = j - 1;
} else {
// 走到这里说明是运算符, 对前一个字符是 +, - ( 的情况做预处理
if(i > 0 && array[i - 1] == '(' || array[i - 1] == '+' || array[i - 1] == '-') {
nums.push(0);
}
// 当一个新的操作数进栈时, 先把栈内优能运算的都算了
// 只有满足 [栈内运算符] 比 [待入栈运算符] 优先级高或者相等的, 才进行运算
while(!opt.isEmpty() && opt.peek() != '(') {
char preOpt = opt.peek();
if(map.get(preOpt) >= map.get(array[i])) {
calc(nums, opt);
} else {
break;
}
}
opt.push(array[i]);
}
}
}
// 出了循环后, 把栈内剩下的部分计算完
while(!opt.isEmpty() && opt.peek() != '(') {
calc(nums, opt);
}
return nums.peek();
}
public void calc(Stack<Integer> nums, Stack<Character> opt) {
if(nums.isEmpty() || nums.size() < 2) {
return;
}
if(opt.isEmpty()) {
return;
}
int num2 = nums.pop(); // 第二个操作数
int num1 = nums.pop(); // 第一个操作数
char op = opt.pop(); // 运算符
int ans = 0;
if(op == '+') {
ans += num1 + num2;
} else if(op == '-') {
ans += num1 - num2;
} else if(op == '*') {
ans += num1 * num2;
}
nums.push(ans); // 计算结果入栈
}
public boolean isNumber(char ch) {
return Character.isDigit(ch);
}
}
对于本题而言, 只让我们计算 +, - * 的运算, 其实我们只要稍微改一下代码, 对于 /, %, ^ 等运算符也是不在话下的.
>>> 我们只需要在维护运算符优先级的 map 中多添加一些需要的运算符, 设置一下优先级, 另外在 calc() 函数中多添加几种运算符的计算方式即可实现!!