单栈
链接 labuladong
设计要点
- 新的符号会触发入栈,当i走到了算式的尽头(i == s.size()),也应该将前面的数字入栈,方便后续计算最终结果。
- 乘除法优先于加减法体现在,乘除法可以和栈顶的数结合,而加减法只能把自己放入栈。
因为括号具有递归性质,无论多少层括号嵌套,通过 calculate 函数递归调用自己。
int index = 0;public int calculateCore(String s) {Stack<Integer> stack = new Stack<>();int num = 0;char sign = '+';while (index < s.length()) {char c = s.charAt(index++);if (isDigit(c)) {num = 10 * num + (c - '0');}if (c == '(') { //递归处理括号num = calculateCore(s);}if ((!isDigit(c) && c != ' ') || index == s.length()) {switch (sign) {case '+':stack.push(num);break;case '-':stack.push(-num);break;case '*': // 拿出前一个数字做对应运算stack.push(stack.pop() * num);break;case '/':stack.push(stack.pop() / num);break;}sign = c; // 更新符号为当前符号,数字清零num = 0;}if (c == ')') break; //退出递归}int res = 0;while (!stack.isEmpty()) {res += stack.pop();}return res;}private boolean isDigit(char c) {return c >= '0' && c <= '9';}
双栈
设计要点:
- 使用两个栈 nums 和 ops :
- nums : 存放所有的数字
- ops :存放所有的数字以外的操作,+/- 也看做是一种操作
- 从前往后做,对遍历到的字符做分情况讨论:
- 空格 : 跳过
- ( : 直接加入 ops 中,等待与之匹配的 )
- ) : 使用现有的 nums 和 ops 进行计算,直到遇到左边最近的一个左括号为止,计算结果放到 nums
- 数字 : 从当前位置开始继续往后取,将整一个连续数字整体取出,加入 nums
- +/- : 需要将操作放入 ops 中。在放入之前先把栈内可以算的都算掉,使用现有的 nums 和 ops 进行计算,直到没有操作或者遇到左括号,计算结果放到 nums
- 使用字典维护一个符号优先级
class Solution {Map<Character, Integer> map = new HashMap<>(){{put('-', 1);put('+', 1);put('*', 2);put('/', 2);put('%', 2);put('^', 3);}};public int calculate(String s) {s = s.replaceAll(" ", ""); // 将所有的空格去掉char[] cs = s.toCharArray();int n = s.length();Deque<Integer> nums = new ArrayDeque<>();nums.addLast(0); // 为了防止第一个数为负数,先往 nums 加个 0Deque<Character> ops = new ArrayDeque<>();for (int i = 0; i < n; i++) {char c = cs[i];if (c == '(') { // 左括号直接入ops栈ops.addLast(c);} else if (c == ')') { // 计算到最近一个左括号为止while (!ops.isEmpty()) {if (ops.peekLast() != '(') {calc(nums, ops);} else {ops.pollLast();break;}}} else {if (isNumber(c)) {int u = 0;int j = i;// 将从 i 位置开始后面的连续数字整体取出,加入 numswhile (j < n && isNumber(cs[j])) u = u * 10 + (cs[j++] - '0');nums.addLast(u);i = j - 1;} else {//为防止 () 内出现的首个字符为运算符,将 (- 替换为 (0-,(+ 替换为 (0+if (i > 0 && (cs[i - 1] == '(' || cs[i - 1] == '+' || cs[i - 1] == '-')) {nums.addLast(0);}while (!ops.isEmpty() && ops.peekLast() != '(') {char prev = ops.peekLast();if (map.get(prev) >= map.get(c)) {calc(nums, ops);} else {break;}}ops.addLast(c);}}}// 有一个新操作要入栈时,先把栈内可以算的都算了while (!ops.isEmpty() && ops.peekLast() != '(') calc(nums, ops);return nums.peekLast();}void calc(Deque<Integer> nums, Deque<Character> ops) {if (nums.isEmpty() || nums.size() < 2) return;if (ops.isEmpty()) return;int b = nums.pollLast(), a = nums.pollLast();char op = ops.pollLast();int ans = 0;if (op == '+') {ans = a + b;} else if (op == '-') {ans = a - b;} else if (op == '*') {ans = a * b;} else if (op == '/') {ans = a / b;} else if (op == '^') {ans = (int)Math.pow(a, b);} else if (op == '%') {ans = a % b;}nums.addLast(ans);}boolean isNumber(char c) {return Character.isDigit(c);}}
