栈:表达式计算

2021-07-28 每日一题 LeetCode

# 0150. 逆波兰表达式求值 (opens new window)

题目

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

解析

遍历逆波兰表达式,使用一个栈来辅助,当遇到一个运算符时,从栈中弹出左右两个操作数,运算之后再入栈,这样遍历结束时,栈中的唯一数字就是运算结果。

代码

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> stk;
        int num1, num2;
        for (int i = 0; i < tokens.size(); i ++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                num2 = stk.top(); stk.pop();
                num1 = stk.top(); stk.pop();
            }
            if (tokens[i] == "+") {
                stk.push(num1 + num2);
            } else if (tokens[i] == "-") {
                stk.push(num1 - num2);
            } else if (tokens[i] == "*") {
                stk.push(num1 * num2);
            } else if (tokens[i] == "/") {
                stk.push(num1 / num2);
            } else {
                stk.push(stoi(tokens[i]));
            }
        }
        return stk.top();
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 0224. 基本计算器 (opens new window)

题目

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

解析

计算器的实现可使用一个通用模板(参考 【宫水三叶】使用「双栈」解决「究极表达式计算」问题 (opens new window)),具体算法如下:

对于「任何表达式」而言,我们都使用两个栈 nums 和 ops:

  • nums : 存放所有的数字;
  • ops :存放所有的数字以外的操作。

然后从前往后做,对遍历到的字符做分情况讨论:

  • 空格:跳过;
  • (:直接加入 ops 中,等待与之匹配的 )
  • ):使用现有的 nums 和 ops 进行计算,直到遇到左边最近的一个左括号为止,计算结果放到 nums;
  • 数字:从当前位置开始继续往后取,将整一个连续数字整体取出,加入 nums;
  • + - * / ^ %:需要将操作放入 ops 中。在放入之前先把栈内可以算的都算掉(只有「栈内运算符」比「当前运算符」优先级高 / 同等,才进行运算),使用现有的 nums 和 ops 进行计算,直到没有操作或者遇到左括号,计算结果放到 nums。

注意

  • 由于第一个数可能是负数,为了减少边界判断。一个小技巧是先往 nums 添加一个 0;
  • 为防止 () 内出现的首个字符为运算符,将所有的空格去掉,并将 (- 替换为 (0-(+ 替换为 (0+
  • 从理论上分析,nums 最好存放的是 long,而不是 int。因为可能存在中间结果溢出,最终答案不溢出的情况。

代码

class Solution {
    unordered_map<char, int> level = {
        {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'%', 2}, {'^', 3}
    };
public:
    int calculate(string s) {
        // 存放所有数字,为了防止第一个数为负数,先添加一个 0
        stack<long> nums;
        nums.push(0);
        
        // 存放所有非数字的操作符
        stack<char> ops;

        int n = s.length();
        for (int i = 0; i < n; i++) {
            if (s[i] == ' ') {
                continue;
            } else if (s[i] == '(') {
                ops.push(s[i]);
            } else if (s[i] == ')') {
                // 计算到最近一个左括号为止
                while (!ops.empty()) {
                    if (ops.top() == '(') {
                        ops.pop();
                        break;
                    }
                    _calc(nums, ops);
                }
            } else if (isdigit(s[i])) {
                int num = s[i++] - '0';
                // 将从 i 位置开始后面的连续数字整体取出,加入 nums
                while (i < n && isdigit(s[i])) {
                    num = num * 10 + (s[i++] - '0');
                }
                nums.push(num);
                i--;
            } else {
                if (i > 0 && s[i - 1] == '(') {
                    nums.push(0);
                }
                // 有一个新操作要入栈时,先把栈内可以算的都算了 
                // 只有满足「栈内运算符」比「当前运算符」优先级高/同等,才进行运算
                while (!ops.empty() && ops.top() != '(') {
                    if (level[ops.top()] < level[s[i]]) {
                        break;
                    }
                    _calc(nums, ops);
                }
                ops.push(s[i]);
            }
        }
        // 将剩余的计算完
        while (!ops.empty()) _calc(nums, ops);
        return nums.top();
    }

    void _calc(stack<long>& nums, stack<char>& ops) {
        if (ops.size() < 1 || nums.size() < 2) return;
        int num2 = nums.top(); nums.pop();
        int num1 = nums.top(); nums.pop();
        int oper = ops.top(); ops.pop();
        int ans = 0;
        if (oper == '+') {
            ans = num1 + num2;
        } else if (oper == '-') {
            ans = num1 - num2;
        } else if (oper == '*') {
            ans = num1 * num2;
        } else if (oper == '/') {
            ans = num1 / num2;
        } else if (oper == '%') {
            ans = num1 % num2;
        } else if (oper == '^') {
            ans = pow(num1, num2);
        }
        nums.push(ans);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# 0227. 基本计算器 II (opens new window)

题目

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

解析

0224. 基本计算器

代码

点击查看代码
class Solution {
    unordered_map<char, int> level = {
        {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'%', 2}, {'^', 3}
    };
public:
    int calculate(string s) {
        // 存放所有数字,为了防止第一个数为负数,先添加一个 0
        stack<long> nums;
        nums.push(0);
        
        // 存放所有非数字的操作符
        stack<char> ops;

        int n = s.length();
        for (int i = 0; i < n; i++) {
            if (s[i] == ' ') {
                continue;
            } else if (s[i] == '(') {
                ops.push(s[i]);
            } else if (s[i] == ')') {
                // 计算到最近一个左括号为止
                while (!ops.empty()) {
                    if (ops.top() == '(') {
                        ops.pop();
                        break;
                    }
                    _calc(nums, ops);
                }
            } else if (isdigit(s[i])) {
                int num = s[i++] - '0';
                // 将从 i 位置开始后面的连续数字整体取出,加入 nums
                while (i < n && isdigit(s[i])) {
                    num = num * 10 + (s[i++] - '0');
                }
                nums.push(num);
                i--;
            } else {
                if (i > 0 && s[i - 1] == '(') {
                    nums.push(0);
                }
                // 有一个新操作要入栈时,先把栈内可以算的都算了 
                // 只有满足「栈内运算符」比「当前运算符」优先级高/同等,才进行运算
                while (!ops.empty() && ops.top() != '(') {
                    if (level[ops.top()] < level[s[i]]) {
                        break;
                    }
                    _calc(nums, ops);
                }
                ops.push(s[i]);
            }
        }
        // 将剩余的计算完
        while (!ops.empty()) _calc(nums, ops);
        return nums.top();
    }

    void _calc(stack<long>& nums, stack<char>& ops) {
        if (ops.size() < 1 || nums.size() < 2) return;
        int num2 = nums.top(); nums.pop();
        int num1 = nums.top(); nums.pop();
        int oper = ops.top(); ops.pop();
        int ans = 0;
        if (oper == '+') {
            ans = num1 + num2;
        } else if (oper == '-') {
            ans = num1 - num2;
        } else if (oper == '*') {
            ans = num1 * num2;
        } else if (oper == '/') {
            ans = num1 / num2;
        } else if (oper == '%') {
            ans = num1 % num2;
        } else if (oper == '^') {
            ans = pow(num1, num2);
        }
        nums.push(ans);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# 0439. 三元表达式解析器 (opens new window)

题目

给定一个以字符串表示的任意嵌套的三元表达式,计算表达式的值。你可以假定给定的表达式始终都是有效的并且只包含数字 0-9, ?, :, T 和 F (T 和 F 分别表示真和假)。

注意:

  • 给定的字符串长度 ≤ 10000。
  • 所包含的数字都只有一位数。
  • 条件表达式从右至左结合(和大多数程序设计语言类似)。
  • 条件是 T 和 F其一,即条件永远不会是数字。
  • 表达式的结果是数字 0-9, T 或者 F。

解析

0150. 逆波兰表达式求值 思路类似,不同点在于本题需要从后向前遍历。

代码

class Solution {
public:
    string parseTernary(string expression) {
        stack<char> stk;
        for (int i = expression.length() - 1; i >= 0; i--) {
            if (!stk.empty() && stk.top() == '?') {
                stk.pop();
                char first = stk.top(); stk.pop();
                stk.pop();
                char second = stk.top(); stk.pop();
                if (expression[i] == 'T') {
                    stk.push(first);
                } else {
                    stk.push(second);
                }
            } else {
                stk.push(expression[i]);
            }
        }
        return string{stk.top()};
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 0772. 基本计算器 III (opens new window)

题目

实现一个基本的计算器来计算简单的表达式字符串。

表达式字符串只包含非负整数,算符 +、-、*、/ ,左括号 ( 和右括号 ) 。整数除法需要 向下截断 。

你可以假定给定的表达式总是有效的。所有的中间结果的范围为 [231-2^{31}, 2312^{31} - 1] 。

解析

0224. 基本计算器

代码

点击查看代码
class Solution {
    unordered_map<char, int> level = {
        {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'%', 2}, {'^', 3}
    };
public:
    int calculate(string s) {
        // 存放所有数字,为了防止第一个数为负数,先添加一个 0
        stack<long> nums;
        nums.push(0);
        
        // 存放所有非数字的操作符
        stack<char> ops;

        int n = s.length();
        for (int i = 0; i < n; i++) {
            if (s[i] == ' ') {
                continue;
            } else if (s[i] == '(') {
                ops.push(s[i]);
            } else if (s[i] == ')') {
                // 计算到最近一个左括号为止
                while (!ops.empty()) {
                    if (ops.top() == '(') {
                        ops.pop();
                        break;
                    }
                    _calc(nums, ops);
                }
            } else if (isdigit(s[i])) {
                int num = s[i++] - '0';
                // 将从 i 位置开始后面的连续数字整体取出,加入 nums
                while (i < n && isdigit(s[i])) {
                    num = num * 10 + (s[i++] - '0');
                }
                nums.push(num);
                i--;
            } else {
                if (i > 0 && s[i - 1] == '(') {
                    nums.push(0);
                }
                // 有一个新操作要入栈时,先把栈内可以算的都算了 
                // 只有满足「栈内运算符」比「当前运算符」优先级高/同等,才进行运算
                while (!ops.empty() && ops.top() != '(') {
                    if (level[ops.top()] < level[s[i]]) {
                        break;
                    }
                    _calc(nums, ops);
                }
                ops.push(s[i]);
            }
        }
        // 将剩余的计算完
        while (!ops.empty()) _calc(nums, ops);
        return nums.top();
    }

    void _calc(stack<long>& nums, stack<char>& ops) {
        if (ops.size() < 1 || nums.size() < 2) return;
        int num2 = nums.top(); nums.pop();
        int num1 = nums.top(); nums.pop();
        int oper = ops.top(); ops.pop();
        int ans = 0;
        if (oper == '+') {
            ans = num1 + num2;
        } else if (oper == '-') {
            ans = num1 - num2;
        } else if (oper == '*') {
            ans = num1 * num2;
        } else if (oper == '/') {
            ans = num1 / num2;
        } else if (oper == '%') {
            ans = num1 % num2;
        } else if (oper == '^') {
            ans = pow(num1, num2);
        }
        nums.push(ans);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
Last Updated: 2023-01-28 4:31:25