224. 基本计算器
思路分析
-
题目大意是给你一个简单字符串s,计算其值。
-
有几个注意点:
- 运算符只有
+ - ( )
。 - 字符串s中有空格。
- 没说数字是个位数,也就是
1+123
这样的表达式是合法的。 - 可能存在 负数 ,也就是
-2+1
这样的表达式是合法的。
- 运算符只有
-
刚开始审题不认真,想用以前本科时期学习时用的 栈 + 逆波兰表达式 。
-
其实注意到了上面的几个注意点就好写了。
我的解题方法
-
首先,对字符串进行预处理:
- 1.去除所有空格
- 2.将字符串s改成
(0+s)
的形式,记为S
(后面会解释为什么)。 - 3.将S中的连续的两个
+
或者-
合并。++
合并成+
+-
合并成-
-+
合并成-
--
合并成+
-
解释上述三点:
- 1.空格没用,所以提前删掉。
- 2.后面解释。
- 3.因为存在 负数 ,因此可能有这样的情况
1 + -2 - +4
的情况,经过1和3处理后会变成1-2-4
。
-
定义两个栈:
stack<char> stk_op
存放运算符。stack<char> stk_num
存放数字。
-
因为栈是 先进后出 ,因此需要 倒序 遍历S。
- 比如
1-2+4
,如果是正序遍历的话:stk_op
中会存放- +
,stk_num
中会存放1 2 4
,运算顺序变成了2+4-1
了,显然不对。 - 如果是倒序遍历的话,
stk_op
中会存放+ -
,stk_num
中会存放4 2 1
,运算顺序是1-2+4
。
- 比如
-
假设当前遍历到
S
的pos
位置,分为如下几种情况:S[pos] = ')'
,此时将’)'压入stk_op
中。S[pos] = '('
,此时,将stk_op
和stk_num
中的运算符和数字进行运算,直到stk_op.top()=')'
为止。- 栗子:比如运算
(1+(2-3)+4)
,显然要优先算(2-3)
,[倒序]遇到’)‘显然不能计算,直到遇到’(’,那么此时stk_op
中是)+)-
,stk_num
中是432
,将stk_num
和stk_op
中的数字进行运算,直到stk_op.top()=')
。也就是运算2-3
。
- 栗子:比如运算
- 遇到数字
0~9
,将数字(不一定是个位数)获取,加入栈stk_num
中。
-
解释2.后面解释。
- 上面的假设是基于
S
中有()
而进行的,如果没有()
,上面的规则是没有办法进行的。因此,我们可以将s
变成(s)
就能保证一定有一对()
。 - 现在的
s
是(s)
,那么还有一种情况(-2+1)
,我们可以添加0+
即可解决,此时S=(0±2-1),经过步骤3即可变成S=(0-2-1)。因此,最终S记为(0+s)
。
- 上面的假设是基于
有了上述思路,可以写出AC代码:
class Solution {
public:
int calculate(string s) {
string S = "(";
s = "0+" + s;
int j = 0;
while (j < s.size()){
if (s[j] == ' '){
j ++;
}else if (s[j] == '+'){
if (j+1 != s.size()){
if (s[j+1] == '+'){
j = j + 2;
S.push_back('+');
}else if (s[j+1] == '-'){
j = j + 2;
S.push_back('-');
}else{
S.push_back('+');
j ++;
}
}else{
S.push_back('+');
j ++;
}
}else if (s[j] == '-'){
if (j+1 != s.size()){
if (s[j+1] == '+'){
j = j + 2;
S.push_back('-');
}else if (s[j+1] == '-'){
j = j + 2;
S.push_back('+');
}else{
S.push_back('-');
j ++;
}
}else{
S.push_back('+');
j ++;
}
}else{
S.push_back(s[j++]);
}
}
S.push_back(')');
stack<char> stk_op;
stack<int> stk_num;
int n = S.size();
int pos = n - 1;
while (pos != -1){
if (S[pos] == ')'){
stk_op.push(')');
pos --;
}else if (S[pos] == '('){
while (!stk_op.empty() && !stk_num.empty()){
if (stk_op.top() == ')'){
stk_op.pop();
break;
}else{
int num1 = stk_num.top();
stk_num.pop();
int num2 = stk_num.top();
stk_num.pop();
int op = stk_op.top();
stk_op.pop();
int ans = 0;
if (op == '+'){
ans = num1 + num2;
}else{
ans = num1 - num2;
}
stk_num.push(ans);
}
}
pos --;
}else if (S[pos] == '+' || S[pos] == '-'){
stk_op.push(S[pos]);
pos --;
}else{
int index = pos;
while (index != 0 && '0' <= S[index] && S[index] <= '9'){
index --;
}
string s_digit = S.substr(index+1, pos-index);
int digit = stoi(s_digit);
stk_num.push(digit);
pos = index;
}
}
return stk_num.top();
}
};
- 很不幸,写了有96行代码。
- 使用了
std:string
中的函数stoi
将一个字符串转化成整数。
执行用时:20 ms, 在所有 C++ 提交中击败了50.04%的用户
内存消耗:10.2 MB, 在所有 C++ 提交中击败了35.76%的用户
- 复杂度分析:
- 时间复杂度:O(n),准确的是O(2*n),遍历一遍,计算一遍。
- 空间复杂度:O(n),使用了两个栈。
LeetCode官方题解
- 运算符只有
+ - ( )
,也就是说数字本身不会因为乘除而变化,只会因为()
的出现而改变符号。 - 维护一个栈ops,这个栈的栈顶记录了当前位置数字的 正负号 。
(1+(2-3)+4-(5-6)) ++ ++ -- + --++++
- 那么我们可以整理出来规则:
- 当遍历到
-
,那么当前的符号要变成栈顶相反的符号。 - 遍历到
+
,当前符号是栈顶的符号。 - 当遇到
(
,将当前符号压栈。 - 遍历到
)
,将栈顶符号弹栈。
- 当遍历到
因此,我们可以正序计算出结果。
class Solution {
public:
int calculate(string s) {
stack<int> ops;
ops.push(1);
int sign = ops.top();
int pos = 0;
int n = s.size();
int ret = 0;
while (pos < n){
if (s[pos] == ' '){
pos ++;
}else if (s[pos] == '-'){
sign = -ops.top();
pos ++;
}else if (s[pos] == '+'){
sign = ops.top();
pos ++;
}else if (s[pos] == '('){
ops.push(sign);
pos ++;
}else if (s[pos] == ')'){
ops.pop();
pos ++;
}else{
long num = 0;
while (pos != n && '0' <= s[pos] && s[pos] <= '9'){
num = num * 10 + s[pos] - '0';
pos ++;
}
ret += sign * num;
}
}
return ret;
}
};
执行用时:12 ms, 在所有 C++ 提交中击败了71.50%的用户
内存消耗:7.8 MB, 在所有 C++ 提交中击败了90.08%的用户
- 复杂度分析:
- 时间复杂度:O(n),一遍遍历。
- 空间复杂度,O(n),使用了一个栈ops。