给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
示例 1:
输入:s = "1 + 1" 输出:2
示例 2:
输入:s = " 2-1 + 2 " 输出:3
示例 3:
输入:s = "(1+(4+5+2)-3)+(6+8)" 输出:23
提示:
1 <= s.length <= 3 * 10^5
s
由数字、'+'
、'-'
、'('
、')'
、和' '
组成s
表示一个有效的表达式- '+' 不能用作一元运算(例如, "+1" 和
"+(2 + 3)"
无效) - '-' 可以用作一元运算(即 "-1" 和
"-(2 + 3)"
是有效的) - 输入中不存在两个连续的操作符
- 每个数字和运行的计算将适合于一个有符号的 32位 整数
解法1:双栈
同类题型:LeetCode 227. 基本计算器 II-CSDN博客 LeetCode 772. 基本计算器 III-CSDN博客
算法逻辑
- 栈的使用:我们使用两个栈,
opStack
用于存放操作符(包括括号),numStack
用于存放数字。栈用于处理操作符的优先级和括号内的计算。 - 字符串预处理:去除字符串中的所有空格,以简化后续的字符读取。
- 遍历字符串:逐个字符读取字符串,根据当前字符的类型(数字或操作符),执行不同的操作。
- 处理数字:当遇到数字时,连续读取形成完整的数字,并将其压入数字栈
numStack
。 - 处理操作符:当遇到操作符时,根据操作符的类型(加号或减号),从操作符栈
opStack
中弹出操作符,并从数字栈numStack
中弹出相应的数字,执行计算,然后将结果压回数字栈。 - 处理括号:遇到左括号时,压入操作符栈;遇到右括号时,进行计算,直到遇到左括号,同时弹出左括号。
算法实现步骤
- 初始化两个栈:操作符栈
opStack
和数字栈numStack
。 - 遍历字符串
s
,对于每个字符c
:- 如果
c
是左括号'('
,则压入操作符栈。 - 如果
c
是右括号')'
,则进行计算,直到遇到左括号,并弹出左括号。 - 如果
c
是数字,则连续读取形成完整数字,并压入数字栈。 - 如果
c
是操作符(加号或减号):- 如果当前位置为字符串开头或前一个字符是左括号,压入数字栈一个初始数字 0。
- 进行必要的计算,将操作符和数字从栈中弹出并执行运算,结果压回数字栈。
- 将当前操作符压入操作符栈。
- 如果
- 完成字符串遍历后,如果操作符栈中仍有操作符,继续进行计算,直到操作符栈为空。
- 最终,数字栈的栈顶元素即为表达式的计算结果。
Java版:
class Solution {
public int calculate(String s) {
// 创建两个栈,分别用于存放数字和操作符
Deque<Character> opStack = new ArrayDeque<>();
Deque<Integer> numStack = new ArrayDeque<>();
// 去掉字符串中的所有空格,方便后续处理
s = s.replaceAll(" ", "");
int i = 0;
int n = s.length();
while (i < s.length()) {
char c = s.charAt(i);
// 如果当前字符是左括号,压入操作符栈
if (c == '(') {
opStack.push(c);
} else if (c == ')') {
// 如果当前字符是右括号,进行计算直到遇到左括号
while (!opStack.isEmpty() && opStack.peek() != '(') {
char op = opStack.pop();
int num2 = numStack.pop();
int num1 = numStack.pop();
if (op == '+') {
numStack.push(num1 + num2);
} else {
numStack.push(num1 - num2);
}
}
// 弹出左括号
opStack.pop();
} else if (Character.isDigit(c)) {
// 如果当前字符是数字,连续读取形成完整数字并压入数字栈
int num = 0;
while (i < n && Character.isDigit(s.charAt(i))) {
num = num * 10 + (s.charAt(i) - '0');
i++;
}
i--; // 此时的i是第一个不为数字的位置,将i调整到i-1
numStack.push(num);
} else {
// ch == '+' || ch == '-' 当前字符是操作符
// 将初始数字0压入数字栈,作为累加的基准,防止第一个数为负数,例如 -1
if (i == 0 || s.charAt(i - 1) == '(') {
numStack.push(0);
}
// 先进行必要的计算,然后将操作符压入操作符栈
while (!opStack.isEmpty() && opStack.peek() != '(') {
char op = opStack.pop();
int num2 = numStack.pop();
int num1 = numStack.pop();
if (op == '+') {
numStack.push(num1 + num2);
} else {
numStack.push(num1 - num2);
}
}
opStack.push(c);
}
i++;
}
// 处理剩余的操作符
while (!opStack.isEmpty() && opStack.peek() != '(') {
char op = opStack.pop();
int num2 = numStack.pop();
int num1 = numStack.pop();
if (op == '+') {
numStack.push(num1 + num2);
} else {
numStack.push(num1 - num2);
}
}
return numStack.peek();
}
}
Python3版:
class Solution:
def calculate(self, s: str) -> int:
# 创建两个栈,分别用于存放数字和操作符
opStack = []
numStack = []
# 去掉字符串中的所有空格,方便后续处理
s = s.replace(" ", "")
i = 0
n = len(s)
while i < n:
# 如果当前字符是左括号,压入操作符栈
if s[i] == '(':
opStack.append(s[i])
elif s[i] == ')':
# 如果当前字符是右括号,进行计算直到遇到左括号
while opStack and opStack[-1] != '(':
op = opStack.pop()
num2 = numStack.pop()
num1 = numStack.pop()
if op == '+':
numStack.append(num1 + num2)
else:
numStack.append(num1 - num2)
# 弹出左括号
opStack.pop()
elif s[i].isdigit():
# 如果当前字符是数字,连续读取形成完整数字并压入数字栈
num = 0
while i < n and s[i].isdigit():
num = num * 10 + int(s[i])
i += 1
i -= 1
numStack.append(num)
else:
# 如果当前字符是操作符,先进行必要的计算,然后将操作符压入操作符栈
# 将初始数字0压入数字栈,作为累加的基准,防止第一个数为负数,例如 -1
if i == 0 or s[i - 1] == '(':
numStack.append(0)
while opStack and opStack[-1] != '(':
op = opStack.pop()
num2 = numStack.pop()
num1 = numStack.pop()
if op == '+':
numStack.append(num1 + num2)
else:
numStack.append(num1 - num2)
opStack.append(s[i])
i += 1
# 处理剩余的操作符
while opStack and opStack[-1] != '(':
op = opStack.pop()
num2 = numStack.pop()
num1 = numStack.pop()
if op == '+':
numStack.append(num1 + num2)
else:
numStack.append(num1 - num2)
return numStack[-1]
复杂度分析
- 时间复杂度:O(n),其中 n 是字符串
s
的长度。这是因为每个字符最多被遍历两次(一次在循环中,一次可能在处理右括号时),所以总体操作次数与字符串长度成正比。 - 空间复杂度:O(n),最坏情况下,所有字符都可能是数字或操作符,需要存储在栈中,因此所需的栈空间与字符串长度成正比。