leetcode:224. 基本计算器

题目

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()

提示:

  • 1 <= s.length <= 3 * 105
  • s 由数字、'+''-''('')'、和 ' ' 组成
  • s 表示一个有效的表达式
  • '+' 不能用作一元运算(例如, "+1""+(2 + 3)" 无效)
  • '-' 可以用作一元运算(即 "-1""-(2 + 3)" 是有效的)
  • 输入中不存在两个连续的操作符
  • 每个数字和运行的计算将适合于一个有符号的 32位 整数

示例:

  1. 输入:s = "1 + 1"
  2. 输出:2
  1. 输入:s = " 2-1 + 2 "
  2. 输出:3
  1. 输入:s = "(1+(4+5+2)-3)+(6+8)"
  2. 输出:23

解答 & 代码

解法一:两个栈

  1. class Solution {
  2. public:
  3. int calculate(string s) {
  4. // 存储每个左括号之前的数值绝对值(若字符串为 '(12+(' 则是存储两个左括号之间的数值绝对值)
  5. stack<int> resultS;
  6. // 存储每个左括号之前的符号:'+' or '-'
  7. stack<char> signS;
  8. int idx = 0; // 当前遍历到的字符串下标
  9. int result = 0; // 当前计算结果,初始化为 0
  10. char curSign = '+'; // 当前的符号,'+' or '-',初始化为 '+'
  11. // 遍历字符串
  12. while(idx < s.size())
  13. {
  14. // 跳过空格
  15. while(idx < s.size() && s[idx] == ' ')
  16. ++idx;
  17. if(idx == s.size()) // 如果已经遍历到尾部则跳出循环,直接结束
  18. break;
  19. /* 跳过空格后,当前的字符有 4 种可能 */
  20. // 1. 如果当前字符是数字,则遍历完当前的这个整数 num
  21. if(s[idx] >= '0' && s[idx] <= '9')
  22. {
  23. int num = 0;
  24. while(idx < s.size() && s[idx] >= '0' && s[idx] <= '9')
  25. {
  26. // 注意下面这行代码的括号,如果不加 s[idx] 先和前面相加可能会溢出
  27. num = num * 10 + (s[idx] - '0');
  28. ++idx;
  29. }
  30. // 根据当前的符号(即当前整数之前的正负号),将当前的计算结果加上 or 减去 num
  31. if(curSign == '+')
  32. result += num;
  33. else if(curSign == '-')
  34. result -= num;
  35. }
  36. // 2. 如果当前字符是 '+' or '-',则改变当前的符号 curSign
  37. else if(s[idx] == '+' || s[idx] == '-')
  38. {
  39. curSign = s[idx];
  40. ++idx;
  41. }
  42. // 3. 如果当前字符是 '(',则将当前的计算结果和符号压入栈,并将计算结果和符号清空
  43. else if(s[idx] == '(')
  44. {
  45. resultS.push(result); // 将当前计算结果入栈
  46. signS.push(curSign); // 将当前符号入栈
  47. result = 0; // 将当前计算结果清零
  48. curSign = '+'; // 将符号位重新初始化为 '+'
  49. ++idx;
  50. }
  51. // 4. 如果当前字符是 ')',则从栈中弹出之前的计算结果和符号,和当前的计算结果合并
  52. else if(s[idx] == ')')
  53. {
  54. // 从栈中取出和当前 ')' 匹配的 '(' 之前的计算结果
  55. int preResult = resultS.top();
  56. resultS.pop();
  57. // 从栈中取出和当前 ')' 匹配的 '(' 之前的符号
  58. char preSign = signS.top();
  59. signS.pop();
  60. // 如果之前的符号为 '+',则将之前的计算结果加上当前的计算结果
  61. if(preSign == '+')
  62. preResult += result;
  63. // 如果之前的符号为 '-',则将之前的计算结果减去当前的计算结果
  64. else if(preSign == '-')
  65. preResult -= result;
  66. // 将当前计算结果更新为合并后的计算结果
  67. result = preResult;
  68. ++idx;
  69. }
  70. }
  71. return result;
  72. }
  73. };

复杂度分析:设字符串 s 长为 N

  • 时间复杂度 O(N):遍历每个字符一次
  • 空间复杂度 O(N):栈空间复杂度,最坏情况下,比如字符串为 "(((((1)))))",有 (N-1)/2 个左括号,则栈中最多需要同时存储 (N-1)/2 个元素

执行结果:

  1. 执行结果:通过
  2. 执行用时:8 ms, 在所有 C++ 提交中击败了 80.79% 的用户
  3. 内存消耗:7.9 MB, 在所有 C++ 提交中击败了 73.99% 的用户

解法二:一个栈存所有整数,递归处理括号

注意这里的一个操作:将字符串存储到双端队列(貌似队列就行?),再对双端队列进行处理

  • 其实也可以不用双端队列,直接处理数组,在 recurCal() 函数传入的参数中增加一个引用参数 int& idx 代表当前处理到的位置,但是 leetcode 提交后发现时空效率都低很多

    1. class Solution {
    2. private:
    3. bool isDigit(char ch)
    4. {
    5. return ch >= '0' && ch <= '9';
    6. }
    7. int recurCal(deque<char>& s)
    8. {
    9. stack<int> numS; // 栈,存放所有整数
    10. char preSign = '+'; // 之前的符号,初始化为加号
    11. // 遍历双端队列,每次弹出队首的一个字符
    12. while(!s.empty())
    13. {
    14. char ch = s.front();
    15. s.pop_front();
    16. // 1. 如果当前是空格,则跳过
    17. if(ch == ' ')
    18. continue;
    19. // 2. 如果当前是加号或减号,则更新 preSign
    20. else if(ch == '+' || ch == '-')
    21. preSign = ch;
    22. // 3. 如果当前是数字,则遍历得到当前的整数,存入栈中
    23. else if(isDigit(ch))
    24. {
    25. int num = ch - '0';
    26. while(!s.empty() && isDigit(s.front()))
    27. {
    28. num = num * 10 + (s.front() - '0');
    29. s.pop_front();
    30. }
    31. if(preSign == '+') // 如果之前的符号是加号,则该整数直接入栈
    32. numS.push(num);
    33. else if(preSign == '-') // 如果之前的符号是减号,则该整数的相反数入栈
    34. numS.push(-num);
    35. }
    36. // 4. 如果当前是遇左括号,则开始递归计算括号内计算后的的 num,存入栈中
    37. else if(ch == '(')
    38. {
    39. int num = recurCal(s);
    40. if(preSign == '+')
    41. numS.push(num);
    42. else if(preSign == '-')
    43. numS.push(-num);
    44. }
    45. // 5. 递归结束条件:如果当前是右括号,则结束遍历,返回递归结果
    46. else if(ch == ')')
    47. break;
    48. }
    49. // 计算栈中所有整数的和,就是最终的计算结果
    50. int result = 0;
    51. while(!numS.empty())
    52. {
    53. result += numS.top();
    54. numS.pop();
    55. }
    56. return result;
    57. }
    58. public:
    59. int calculate(string s) {
    60. // 将字符串的字符存入双端队列
    61. deque<char> sq;
    62. for(int i = 0; i < s.size(); ++i)
    63. sq.push_back(s[i]);
    64. return recurCal(sq);
    65. }
    66. };

    执行结果: ``` 执行结果:通过

执行用时:16 ms, 在所有 C++ 提交中击败了 44.27% 的用户 内存消耗:11.6 MB, 在所有 C++ 提交中击败了 17.01% 的用户 ```