LeetCode 第224题:基本计算器
题目描述
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
注意:
s
由数字、'+'
、'-'
、'('
、')'
、和' '
组成s
表示一个有效的表达式
难度
困难
题目链接
示例
示例 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)"
是有效的)- 输入中不存在两个连续的操作符
- 每个数字和运行的计算结果都在
[-2^31, 2^31 - 1]
范围内
解题思路
这道题要求实现一个基本计算器,支持加减运算和括号表达式。实现这样的计算器,我们有以下几种方法:
方法一:栈 + 递归
- 使用栈来存储操作数和操作符
- 遇到左括号时进行递归计算,返回括号内表达式的值
- 返回到上层递归后,将计算结果作为操作数处理
时间复杂度:O(n),其中 n 是字符串的长度
空间复杂度:O(n),主要是递归调用的栈空间
方法二:栈 + 符号
- 使用一个栈来存储每层括号的符号
- 维护一个当前结果 result 和当前操作数 num
- 当遇到数字时,累积到当前操作数
- 当遇到 ‘+’ 或 ‘-’ 时,计算当前操作数的符号并添加到结果中
- 当遇到左括号时,将当前结果和符号压入栈中,然后重置当前结果
- 当遇到右括号时,从栈中弹出符号和之前的结果,计算当前括号内的总和
时间复杂度:O(n)
空间复杂度:O(n)
代码实现
C# 实现
public class Solution {
public int Calculate(string s) {
Stack<int> stack = new Stack<int>();
int result = 0;
int number = 0;
int sign = 1; // 1表示正号,-1表示负号
for (int i = 0; i < s.Length; i++) {
char c = s[i];
if (char.IsDigit(c)) {
// 解析数字,可能有多位
number = number * 10 + (c - '0');
} else if (c == '+') {
// 将当前数字(带符号)加入结果
result += sign * number;
number = 0;
sign = 1;
} else if (c == '-') {
// 将当前数字(带符号)加入结果
result += sign * number;
number = 0;
sign = -1;
} else if (c == '(') {
// 保存当前结果和符号到栈中
stack.Push(result);
stack.Push(sign);
// 重置结果和符号,处理括号内的表达式
result = 0;
sign = 1;
} else if (c == ')') {
// 将当前数字(带符号)加入结果
result += sign * number;
number = 0;
// 从栈中取出符号和之前的结果
result *= stack.Pop(); // 应用之前的符号
result += stack.Pop(); // 加上之前的结果
}
// 忽略空格
}
// 处理最后一个数字
if (number != 0) {
result += sign * number;
}
return result;
}
}
Python 实现
class Solution:
def calculate(self, s: str) -> int:
stack = []
result = 0
number = 0
sign = 1 # 1表示正号,-1表示负号
for char in s:
if char.isdigit():
# 解析数字,可能有多位
number = number * 10 + int(char)
elif char == '+':
# 将当前数字(带符号)加入结果
result += sign * number
number = 0
sign = 1
elif char == '-':
# 将当前数字(带符号)加入结果
result += sign * number
number = 0
sign = -1
elif char == '(':
# 保存当前结果和符号到栈中
stack.append(result)
stack.append(sign)
# 重置结果和符号,处理括号内的表达式
result = 0
sign = 1
elif char == ')':
# 将当前数字(带符号)加入结果
result += sign * number
number = 0
# 从栈中取出符号和之前的结果
result *= stack.pop() # 应用之前的符号
result += stack.pop() # 加上之前的结果
# 忽略空格
# 处理最后一个数字
if number != 0:
result += sign * number
return result
C++ 实现
class Solution {
public:
int calculate(string s) {
stack<int> st;
int result = 0;
int number = 0;
int sign = 1; // 1表示正号,-1表示负号
for (int i = 0; i < s.length(); i++) {
char c = s[i];
if (isdigit(c)) {
// 解析数字,可能有多位
number = number * 10 + (c - '0');
} else if (c == '+') {
// 将当前数字(带符号)加入结果
result += sign * number;
number = 0;
sign = 1;
} else if (c == '-') {
// 将当前数字(带符号)加入结果
result += sign * number;
number = 0;
sign = -1;
} else if (c == '(') {
// 保存当前结果和符号到栈中
st.push(result);
st.push(sign);
// 重置结果和符号,处理括号内的表达式
result = 0;
sign = 1;
} else if (c == ')') {
// 将当前数字(带符号)加入结果
result += sign * number;
number = 0;
// 从栈中取出符号和之前的结果
result *= st.top(); // 应用之前的符号
st.pop();
result += st.top(); // 加上之前的结果
st.pop();
}
// 忽略空格
}
// 处理最后一个数字
if (number != 0) {
result += sign * number;
}
return result;
}
};
性能分析
各语言实现的性能对比:
实现语言 | 执行用时 | 内存消耗 | 说明 |
---|---|---|---|
C# | 84 ms | 40.5 MB | 效率较高,使用栈处理括号嵌套 |
Python | 92 ms | 16.4 MB | 实现清晰,但性能略低于其他语言 |
C++ | 8 ms | 8.0 MB | 性能最优,内存消耗少 |
补充说明
代码亮点
- 使用栈处理括号嵌套,避免了递归调用的复杂性
- 使用符号变量(sign)来处理正负号,简化了代码逻辑
- 对输入进行单次遍历,保证了 O(n) 的时间复杂度
- 代码结构清晰,易于理解和扩展
优化方向
- 可以预先分配栈空间,减少动态分配的开销
- 可以使用字符数组代替字符串,提高访问效率
- 对于C#和Python实现,可以考虑使用更高效的数据结构
- 在处理大量连续数字时,可以优化数字解析逻辑
解题难点
- 处理括号嵌套,需要正确保存和恢复计算状态
- 正确处理操作符的优先级,尤其是负号的处理
- 解析多位数字,而不是单个字符
- 处理输入中的空格和其他无关字符
常见错误
- 忽略处理最后一个数字
- 栈操作顺序错误,尤其是保存和恢复结果时
- 符号处理不当,例如连续的加减号
- 括号匹配错误,导致计算结果不正确
- 未考虑空字符串或只包含空格的输入
相关题目
- 227. 基本计算器 II - 支持加减乘除的计算器
- 772. 基本计算器 III - 支持加减乘除和括号的计算器
- 150. 逆波兰表达式求值 - 使用栈评估后缀表达式
- 394. 字符串解码 - 使用栈处理嵌套结构