题目
标题和出处
标题:基本计算器
出处:224. 基本计算器
难度
7 级
题目描述
要求
给你一个字符串 s \texttt{s} s,表示一个有效的表达式,请你实现一个基本计算器来计算并返回它的值。
注意:你不允许使用任何内置库函数计算数学表达式,例如 eval() \texttt{eval()} eval()。
示例
示例 1:
输入:
s
=
"1
+
1"
\texttt{s = "1 + 1"}
s = "1 + 1"
输出:
2
\texttt{2}
2
示例 2:
输入:
s
=
"
2-1
+
2
"
\texttt{s = " 2-1 + 2 "}
s = " 2-1 + 2 "
输出:
3
\texttt{3}
3
示例 3:
输入:
s
=
"(1+(4+5+2)-3)+(6+8)"
\texttt{s = "(1+(4+5+2)-3)+(6+8)"}
s = "(1+(4+5+2)-3)+(6+8)"
输出:
23
\texttt{23}
23
数据范围
- 1 ≤ s.length ≤ 3 × 10 5 \texttt{1} \le \texttt{s.length} \le \texttt{3} \times \texttt{10}^\texttt{5} 1≤s.length≤3×105
- s \texttt{s} s 由数字、 ‘+’ \texttt{`+'} ‘+’、 ‘-’ \texttt{`-'} ‘-’、 ‘(’ \texttt{`('} ‘(’、 ‘)’ \texttt{`)'} ‘)’ 和 ‘ ’ \texttt{` '} ‘ ’ 组成
- s \texttt{s} s 表示一个有效的表达式
- ‘+’ \texttt{`+'} ‘+’ 不会用作一元运算符( "+1" \texttt{"+1"} "+1" 和 "+(2 + 3)" \texttt{"+(2 + 3)"} "+(2 + 3)" 是无效的)
- ‘-’ \texttt{`-'} ‘-’ 可以用作一元运算符( "-1" \texttt{"-1"} "-1" 和 "-(2 + 3)" \texttt{"-(2 + 3)"} "-(2 + 3)" 是有效的)
- 输入不包含两个连续的运算符
- 每个数和所有运算结果都在 32 \texttt{32} 32 位有符号整数范围内
解法
思路和算法
计算数学表达式的值,可以使用栈。这道题的数学表达式包含数字、加减号和括号,可行的方法有多种,列举如下:
-
将数学表达式转成逆波兰表达式,然后利用「逆波兰表达式求值」的解法计算数学表达式的值;
-
将括号展开,同时在必要的时候改变括号内部的运算符,当全部括号都展开之后即可按照从左到右的顺序计算数学表达式的值;
-
如果有括号,依次计算从内到外每一层括号内的数学表达式的值,然后计算整个数学表达式的值。
上述方法有不同的适用场景。第 1 种方法可以处理的数学表达式的范围较广,四则运算和括号的情况都可以处理,但是需要做两步操作,逻辑较为复杂。第 2 种方法和第 3 种方法可以处理的数学表达式有局限性,但是逻辑较为简单。
此处提供的解法是上述第 3 种方法。使用两个栈分别存储数字和运算符,遍历数学表达式的过程中维护两个栈的元素并进行相应的计算。
由于给定的数学表达式是有效的表达式,因此不需要做容错处理。从左到右遍历数学表达式,遇到空格则跳过,遇到数字、加减号和括号则做相应的处理。具体做法如下:
-
遇到数字,则将当前数字遍历完毕,然后将当前数字入数字栈;
-
遇到左括号,则将左括号入运算符栈;
-
遇到右括号,则需要检查运算符栈的栈顶是否为加号或减号,如果成立则将数字栈的两个数字出栈以及将运算符栈的一个运算符出栈,执行运算并将运算结果入数字栈,然后将运算符栈的栈顶左括号出栈;
-
遇到加减号,则需要做以下两步操作:
-
如果当前下标是 0 0 0 或者前一个字符是左括号,则当前运算符是一元运算符,为了方便处理,将 0 0 0 入数字栈;
-
如果运算符栈不为空且运算符栈的栈顶为加号或减号,则将数字栈的两个数字出栈以及将运算符栈的一个运算符出栈,执行运算并将运算结果入数字栈;
-
将当前运算符入运算符栈。
-
遍历完数学表达式之后,可能两个栈内仍然有元素,因此需要对两个栈内的剩余元素继续计算,最后数字栈内只剩下一个元素,该元素即为数学表达式的值。
上述过程中,为了确保同级运算符的运算顺序为从左到右依次运算,每次遇到右括号和加减号时都会首先检查运算符栈的栈顶元素,如果是加号或减号即执行运算,因此在任何情况下,运算符栈内不可能出现连续两个加减号(即连续的两个元素中至少有一个是左括号)。
考虑以下例子: s = “–2+1+(–5+11+(4+8)+18)" s = \text{``--2+1+(--5+11+(4+8)+18)"} s=“–2+1+(–5+11+(4+8)+18)"。数学表达式的计算过程如下。
-
由于下标 0 0 0 处的字符是减号,因此将 0 0 0 入数字栈,将减号入运算符栈。数字栈为 [ 0 ] [0] [0],运算符栈为 [ ‘–’ ] [\text{`--'}] [‘–’],其中左边为栈底,右边为栈顶。
-
遇到数字 2 2 2,将 2 2 2 入数字栈。数字栈为 [ 0 , 2 ] [0, 2] [0,2],运算符栈为 [ ‘–’ ] [\text{`--'}] [‘–’]。
-
遇到加号,由于运算符栈的栈顶是减号,因此将 2 2 2 和 0 0 0 出数字栈,将减号出运算符栈,将 0 − 2 = − 2 0 - 2 = -2 0−2=−2 入数字栈,将加号入运算符栈。数字栈为 [ − 2 ] [-2] [−2],运算符栈为 [ ‘+’ ] [\text{`+'}] [‘+’]。
-
遇到数字 1 1 1,将 1 1 1 入数字栈。数字栈为 [ − 2 , 1 ] [-2, 1] [−2,1],运算符栈为 [ ‘+’ ] [\text{`+'}] [‘+’]。
-
遇到加号,由于运算符栈的栈顶是加号,因此将 1 1 1 和 − 2 -2 −2 出数字栈,将加号出运算符栈,将 ( − 2 ) + 1 = − 1 (-2) + 1 = -1 (−2)+1=−1 入数字栈,将加号入运算符栈。数字栈为 [ − 1 ] [-1] [−1],运算符栈为 [ ‘+’ ] [\text{`+'}] [‘+’]。
-
遇到左括号,将左括号入运算符栈。数字栈为 [ − 1 ] [-1] [−1],运算符栈为 [ ‘+’ , ‘(’ ] [\text{`+'}, \text{`('}] [‘+’,‘(’]。
-
遇到减号,由于前一个字符是左括号,因此将 0 0 0 入数字栈,将减号入运算符栈。数字栈为 [ − 1 , 0 ] [-1, 0] [−1,0],运算符栈为 [ ‘+’ , ‘(’ , ‘–’ ] [\text{`+'}, \text{`('}, \text{`--'}] [‘+’,‘(’,‘–’]。
-
遇到数字 5 5 5,将 5 5 5 入数字栈。数字栈为 [ − 1 , 0 , 5 ] [-1, 0, 5] [−1,0,5],运算符栈为 [ ‘+’ , ‘(’ , ‘–’ ] [\text{`+'}, \text{`('}, \text{`--'}] [‘+’,‘(’,‘–’]。
-
遇到加号,由于运算符栈的栈顶是减号,因此将 5 5 5 和 0 0 0 出数字栈,将减号出运算符栈,将 0 − 5 = − 5 0 - 5 = -5 0−5=−5 入数字栈,将加号入运算符栈。数字栈为 [ − 1 , − 5 ] [-1, -5] [−1,−5],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’]。
-
遇到数字 11 11 11,将 11 11 11 入数字栈。数字栈为 [ − 1 , − 5 , 11 ] [-1, -5, 11] [−1,−5,11],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’]。
-
遇到加号,由于运算符栈的栈顶是加号,因此将 11 11 11 和 − 5 -5 −5 出数字栈,将加号出运算符栈,将 ( − 5 ) + 11 = 6 (-5) + 11 = 6 (−5)+11=6 入数字栈,将加号入运算符栈。数字栈为 [ − 1 , 6 ] [-1, 6] [−1,6],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’]。
-
遇到左括号,将左括号入运算符栈。数字栈为 [ − 1 , 6 ] [-1, 6] [−1,6],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ , ‘(’ ] [\text{`+'}, \text{`('}, \text{`+'}, \text{`('}] [‘+’,‘(’,‘+’,‘(’]。
-
遇到数字 4 4 4,将 4 4 4 入数字栈。数字栈为 [ − 1 , 6 , 4 ] [-1, 6, 4] [−1,6,4],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ , ‘(’ ] [\text{`+'}, \text{`('}, \text{`+'}, \text{`('}] [‘+’,‘(’,‘+’,‘(’]。
-
遇到加号,由于运算符栈的栈顶是左括号,因此直接将加号入运算符栈。数字栈为 [ − 1 , 6 , 4 ] [-1, 6, 4] [−1,6,4],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’,‘(’,‘+’]。
-
遇到数字 8 8 8,将 8 8 8 入数字栈。数字栈为 [ − 1 , 6 , 4 , 8 ] [-1, 6, 4, 8] [−1,6,4,8],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’,‘(’,‘+’]。
-
遇到右括号,由于运算符栈的栈顶是加号,因此将 8 8 8 和 4 4 4 出数字栈,将加号出运算符栈,将 4 + 8 = 12 4 + 8 = 12 4+8=12 入数字栈,将左括号出运算符栈。数字栈为 [ − 1 , 6 , 12 ] [-1, 6, 12] [−1,6,12],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’]。
-
遇到加号,由于运算符栈的栈顶是加号,因此将 12 12 12 和 6 6 6 出数字栈,将加号出运算符栈,将 6 + 12 = 18 6 + 12 = 18 6+12=18 入数字栈,将加号入运算符栈。数字栈为 [ − 1 , 18 ] [-1, 18] [−1,18],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’]。
-
遇到数字 18 18 18,将 18 18 18 入数字栈。数字栈为 [ − 1 , 18 , 18 ] [-1, 18, 18] [−1,18,18],运算符栈为 [ ‘+’ , ‘(’ , ‘+’ ] [\text{`+'}, \text{`('}, \text{`+'}] [‘+’,‘(’,‘+’]。
-
遇到右括号,由于运算符栈的栈顶是加号,因此将 18 18 18 和 18 18 18 出数字栈,将加号出运算符栈,将 18 + 18 = 36 18 + 18 = 36 18+18=36 入数字栈,将左括号出运算符栈。数字栈为 [ − 1 , 36 ] [-1, 36] [−1,36],运算符栈为 [ ‘+’ ] [\text{`+'}] [‘+’]。
-
遍历结束,由于两个栈内还有元素,因此将 36 36 36 和 − 1 -1 −1 出数字栈,将加号出运算符栈,将 ( − 1 ) + 36 = 35 (-1) + 36 = 35 (−1)+36=35 入数字栈。数字栈为 [ 35 ] [35] [35],运算符栈为 [ ] [] []。
-
数字栈内只剩下一个元素 35 35 35,数学表达式的值为 35 35 35。
代码
class Solution {
public int calculate(String s) {
Deque<Integer> numStack = new ArrayDeque<Integer>();
Deque<Character> opStack = new ArrayDeque<Character>();
int length = s.length();
int index = 0;
char prev = '(';
while (index < length) {
char c = s.charAt(index);
if (c == ' ') {
index++;
continue;
} else if (Character.isDigit(c)) {
int num = 0;
while (index < length && Character.isDigit(s.charAt(index))) {
num = num * 10 + s.charAt(index) - '0';
index++;
}
numStack.push(num);
} else {
if (c == '(') {
opStack.push(c);
} else if (c == ')') {
calculate(numStack, opStack);
opStack.pop();
} else {
if (prev == '(') {
numStack.push(0);
}
calculate(numStack, opStack);
opStack.push(c);
}
index++;
}
prev = c;
}
calculate(numStack, opStack);
return numStack.pop();
}
public void calculate(Deque<Integer> numStack, Deque<Character> opStack) {
if (!opStack.isEmpty() && (opStack.peek() == '+' || opStack.peek() == '-')) {
char op = opStack.pop();
int num2 = numStack.pop();
int num1 = numStack.pop();
switch (op) {
case '+':
numStack.push(num1 + num2);
break;
case '-':
numStack.push(num1 - num2);
break;
default:
}
}
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。需要遍历字符串 s s s 一次,每个字符的操作时间都是 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。空间复杂度主要取决于栈空间,栈内元素个数不会超过 n n n。