单栈

链接 labuladong


设计要点

  1. 新的符号会触发入栈,当i走到了算式的尽头(i == s.size()),也应该将前面的数字入栈,方便后续计算最终结果。
  2. 乘除法优先于加减法体现在,乘除法可以和栈顶的数结,而加减法只能把自己放入栈。
  3. 因为括号具有递归性质,无论多少层括号嵌套,通过 calculate 函数递归调用自己。

    1. int index = 0;
    2. public int calculateCore(String s) {
    3. Stack<Integer> stack = new Stack<>();
    4. int num = 0;
    5. char sign = '+';
    6. while (index < s.length()) {
    7. char c = s.charAt(index++);
    8. if (isDigit(c)) {
    9. num = 10 * num + (c - '0');
    10. }
    11. if (c == '(') { //递归处理括号
    12. num = calculateCore(s);
    13. }
    14. if ((!isDigit(c) && c != ' ') || index == s.length()) {
    15. switch (sign) {
    16. case '+':
    17. stack.push(num);
    18. break;
    19. case '-':
    20. stack.push(-num);
    21. break;
    22. case '*': // 拿出前一个数字做对应运算
    23. stack.push(stack.pop() * num);
    24. break;
    25. case '/':
    26. stack.push(stack.pop() / num);
    27. break;
    28. }
    29. sign = c; // 更新符号为当前符号,数字清零
    30. num = 0;
    31. }
    32. if (c == ')') break; //退出递归
    33. }
    34. int res = 0;
    35. while (!stack.isEmpty()) {
    36. res += stack.pop();
    37. }
    38. return res;
    39. }
    40. private boolean isDigit(char c) {
    41. return c >= '0' && c <= '9';
    42. }

    双栈


设计要点:

  1. 使用两个栈 nums 和 ops :
    • nums : 存放所有的数字
    • ops :存放所有的数字以外的操作,+/- 也看做是一种操作
  2. 从前往后做,对遍历到的字符做分情况讨论:
    • 空格 : 跳过
    • ( : 直接加入 ops 中,等待与之匹配的 )
    • ) : 使用现有的 nums 和 ops 进行计算,直到遇到左边最近的一个左括号为止,计算结果放到 nums
    • 数字 : 从当前位置开始继续往后取,将整一个连续数字整体取出,加入 nums
    • +/- : 需要将操作放入 ops 中。在放入之前先把栈内可以算的都算掉,使用现有的 nums 和 ops 进行计算,直到没有操作或者遇到左括号,计算结果放到 nums
  3. 使用字典维护一个符号优先级
  1. class Solution {
  2. Map<Character, Integer> map = new HashMap<>(){{
  3. put('-', 1);
  4. put('+', 1);
  5. put('*', 2);
  6. put('/', 2);
  7. put('%', 2);
  8. put('^', 3);
  9. }};
  10. public int calculate(String s) {
  11. s = s.replaceAll(" ", ""); // 将所有的空格去掉
  12. char[] cs = s.toCharArray();
  13. int n = s.length();
  14. Deque<Integer> nums = new ArrayDeque<>();
  15. nums.addLast(0); // 为了防止第一个数为负数,先往 nums 加个 0
  16. Deque<Character> ops = new ArrayDeque<>();
  17. for (int i = 0; i < n; i++) {
  18. char c = cs[i];
  19. if (c == '(') { // 左括号直接入ops栈
  20. ops.addLast(c);
  21. } else if (c == ')') { // 计算到最近一个左括号为止
  22. while (!ops.isEmpty()) {
  23. if (ops.peekLast() != '(') {
  24. calc(nums, ops);
  25. } else {
  26. ops.pollLast();
  27. break;
  28. }
  29. }
  30. } else {
  31. if (isNumber(c)) {
  32. int u = 0;
  33. int j = i;
  34. // 将从 i 位置开始后面的连续数字整体取出,加入 nums
  35. while (j < n && isNumber(cs[j])) u = u * 10 + (cs[j++] - '0');
  36. nums.addLast(u);
  37. i = j - 1;
  38. } else {
  39. //为防止 () 内出现的首个字符为运算符,将 (- 替换为 (0-,(+ 替换为 (0+
  40. if (i > 0 && (cs[i - 1] == '(' || cs[i - 1] == '+' || cs[i - 1] == '-')) {
  41. nums.addLast(0);
  42. }
  43. while (!ops.isEmpty() && ops.peekLast() != '(') {
  44. char prev = ops.peekLast();
  45. if (map.get(prev) >= map.get(c)) {
  46. calc(nums, ops);
  47. } else {
  48. break;
  49. }
  50. }
  51. ops.addLast(c);
  52. }
  53. }
  54. }
  55. // 有一个新操作要入栈时,先把栈内可以算的都算了
  56. while (!ops.isEmpty() && ops.peekLast() != '(') calc(nums, ops);
  57. return nums.peekLast();
  58. }
  59. void calc(Deque<Integer> nums, Deque<Character> ops) {
  60. if (nums.isEmpty() || nums.size() < 2) return;
  61. if (ops.isEmpty()) return;
  62. int b = nums.pollLast(), a = nums.pollLast();
  63. char op = ops.pollLast();
  64. int ans = 0;
  65. if (op == '+') {
  66. ans = a + b;
  67. } else if (op == '-') {
  68. ans = a - b;
  69. } else if (op == '*') {
  70. ans = a * b;
  71. } else if (op == '/') {
  72. ans = a / b;
  73. } else if (op == '^') {
  74. ans = (int)Math.pow(a, b);
  75. } else if (op == '%') {
  76. ans = a % b;
  77. }
  78. nums.addLast(ans);
  79. }
  80. boolean isNumber(char c) {
  81. return Character.isDigit(c);
  82. }
  83. }