|
| 1 | +# [0770. 基本计算器 IV](https://leetcode.cn/problems/basic-calculator-iv/) |
| 2 | + |
| 3 | +- 标签:栈、递归、哈希表、数学、字符串 |
| 4 | +- 难度:困难 |
| 5 | + |
| 6 | +## 题目链接 |
| 7 | + |
| 8 | +- [0770. 基本计算器 IV - 力扣](https://leetcode.cn/problems/basic-calculator-iv/) |
| 9 | + |
| 10 | +## 题目大意 |
| 11 | + |
| 12 | +**描述**: |
| 13 | + |
| 14 | +给定一个表达式如 `expression = "e + 8 - a + 5"` 和一个求值映射,如 `{"e": 1}`(给定的形式为 `evalvars = ["e"]` 和 `evalints = [1]`。 |
| 15 | + |
| 16 | +**要求**: |
| 17 | + |
| 18 | +返回表示简化表达式的标记列表,例如 `["-1*a","14"]`。 |
| 19 | + |
| 20 | +- 表达式交替使用块和符号,每个块和符号之间有一个空格。 |
| 21 | +- 块要么是括号中的表达式,要么是变量,要么是非负整数。 |
| 22 | +- 变量是一个由小写字母组成的字符串(不包括数字)。请注意,变量可以是多个字母,并注意变量从不具有像 `"2x"` 或 `"-x"` 这样的前导系数或一元运算符。 |
| 23 | + |
| 24 | +表达式按通常顺序进行求值:先是括号,然后求乘法,再计算加法和减法。 |
| 25 | + |
| 26 | +- 例如,`expression = "1 + 2 * 3"` 的答案是 ["7"]。 |
| 27 | + |
| 28 | +输出格式如下: |
| 29 | + |
| 30 | +- 对于系数非零的每个自变量项,我们按字典排序的顺序将自变量写在一个项中。 |
| 31 | +- 例如,我们永远不会写像 `"b*a*c"` 这样的项,只写 `"a*b*c"`。 |
| 32 | +- 项的次数等于被乘的自变量的数目,并计算重复项。我们先写出答案的最大次数项,用字典顺序打破关系,此时忽略词的前导系数。 |
| 33 | +- 例如,`"a*a*b*c"` 的次数为 $4$。 |
| 34 | +- 项的前导系数直接放在左边,用星号将它与变量分隔开(如果存在的话)。前导系数 $1$ 仍然要打印出来。 |
| 35 | +- 格式良好的一个示例答案是 `["-2*a*a*a", "3*a*a*b", "3*b*b", "4*a", "5*c", "-6"]`。 |
| 36 | +- 系数为 $0$ 的项(包括常数项)不包括在内。 |
| 37 | +- 例如,`"0"` 的表达式输出为 `[]`。 |
| 38 | + |
| 39 | +注意:你可以假设给定的表达式均有效。所有中间结果都在区间 $[-2^{31}, 2^{31} - 1]$ 内。 |
| 40 | + |
| 41 | +**说明**: |
| 42 | + |
| 43 | +- $1 \le expression.length \le 250$。 |
| 44 | +- expression 由小写英文字母,数字 '+', '-', '*', '(', ')', ' ' 组成。 |
| 45 | +- expression 不包含任何前空格或后空格。 |
| 46 | +- expression 中的所有符号都用一个空格隔开。 |
| 47 | +- $0 \le evalvars.length \le 10^{3}$。 |
| 48 | +- $1 \le evalvars[i].length \le 20$。 |
| 49 | +- $evalvars[i]$ 由小写英文字母组成。 |
| 50 | +- $evalints.length == evalvars.length$。 |
| 51 | +- $-10^{3} \le evalints[i] \le 10^{3}$。 |
| 52 | + |
| 53 | +**示例**: |
| 54 | + |
| 55 | +- 示例 1: |
| 56 | + |
| 57 | +```python |
| 58 | +输入:expression = "e + 8 - a + 5", evalvars = ["e"], evalints = [1] |
| 59 | +输出:["-1*a","14"] |
| 60 | +``` |
| 61 | + |
| 62 | +- 示例 2: |
| 63 | + |
| 64 | +```python |
| 65 | +输入:expression = "e - 8 + temperature - pressure", |
| 66 | +evalvars = ["e", "temperature"], evalints = [1, 12] |
| 67 | +输出:["-1*pressure","5"] |
| 68 | +``` |
| 69 | + |
| 70 | +## 解题思路 |
| 71 | + |
| 72 | +### 思路 1:递归 + 哈希表 + 多项式运算 |
| 73 | + |
| 74 | +这道题要求实现一个支持变量的计算器,需要处理加减乘运算、括号和变量替换。 |
| 75 | + |
| 76 | +核心思路: |
| 77 | + |
| 78 | +1. 定义多项式类,支持加减乘运算。 |
| 79 | +2. 多项式用字典表示,键是变量的元组(按字典序排序),值是系数。 |
| 80 | +3. 使用递归下降解析表达式。 |
| 81 | +4. 先将给定的变量替换为常数,再进行计算。 |
| 82 | + |
| 83 | +算法步骤: |
| 84 | + |
| 85 | +1. 创建多项式类 `Poly`,支持: |
| 86 | + - 加法:合并同类项。 |
| 87 | + - 减法:系数取反后加法。 |
| 88 | + - 乘法:分配律展开。 |
| 89 | +2. 解析表达式: |
| 90 | + - 使用递归下降解析器处理括号、加减乘运算。 |
| 91 | + - 遇到变量时,如果在求值映射中,替换为常数;否则保留为变量。 |
| 92 | +3. 格式化输出: |
| 93 | + - 按次数从高到低、字典序排序。 |
| 94 | + - 格式化每一项。 |
| 95 | + |
| 96 | +### 思路 1:代码 |
| 97 | + |
| 98 | +```python |
| 99 | +class Solution: |
| 100 | + def basicCalculatorIV(self, expression: str, evalvars: List[str], evalints: List[int]) -> List[str]: |
| 101 | + from collections import Counter |
| 102 | + |
| 103 | + # 创建变量求值映射 |
| 104 | + eval_map = dict(zip(evalvars, evalints)) |
| 105 | + |
| 106 | + # 多项式类 |
| 107 | + class Poly: |
| 108 | + def __init__(self, terms=None): |
| 109 | + # terms: {变量元组: 系数} |
| 110 | + self.terms = Counter(terms) if terms else Counter() |
| 111 | + |
| 112 | + def __add__(self, other): |
| 113 | + result = Poly(self.terms) |
| 114 | + for key, val in other.terms.items(): |
| 115 | + result.terms[key] += val |
| 116 | + return result |
| 117 | + |
| 118 | + def __sub__(self, other): |
| 119 | + result = Poly(self.terms) |
| 120 | + for key, val in other.terms.items(): |
| 121 | + result.terms[key] -= val |
| 122 | + return result |
| 123 | + |
| 124 | + def __mul__(self, other): |
| 125 | + result = Poly() |
| 126 | + for k1, v1 in self.terms.items(): |
| 127 | + for k2, v2 in other.terms.items(): |
| 128 | + # 合并变量(按字典序排序) |
| 129 | + key = tuple(sorted(k1 + k2)) |
| 130 | + result.terms[key] += v1 * v2 |
| 131 | + return result |
| 132 | + |
| 133 | + def to_list(self): |
| 134 | + # 转换为输出格式 |
| 135 | + # 删除系数为 0 的项 |
| 136 | + items = [(k, v) for k, v in self.terms.items() if v != 0] |
| 137 | + # 排序:先按次数降序,再按字典序 |
| 138 | + items.sort(key=lambda x: (-len(x[0]), x[0])) |
| 139 | + |
| 140 | + result = [] |
| 141 | + for vars_tuple, coef in items: |
| 142 | + if vars_tuple: |
| 143 | + result.append(f"{coef}*{'*'.join(vars_tuple)}") |
| 144 | + else: |
| 145 | + result.append(str(coef)) |
| 146 | + return result |
| 147 | + |
| 148 | + # 解析表达式 |
| 149 | + tokens = expression.replace('(', ' ( ').replace(')', ' ) ').split() |
| 150 | + |
| 151 | + def parse(): |
| 152 | + """解析加减表达式""" |
| 153 | + nonlocal idx |
| 154 | + left = parse_term() |
| 155 | + |
| 156 | + while idx < len(tokens) and tokens[idx] in ['+', '-']: |
| 157 | + op = tokens[idx] |
| 158 | + idx += 1 |
| 159 | + right = parse_term() |
| 160 | + if op == '+': |
| 161 | + left = left + right |
| 162 | + else: |
| 163 | + left = left - right |
| 164 | + |
| 165 | + return left |
| 166 | + |
| 167 | + def parse_term(): |
| 168 | + """解析乘法表达式""" |
| 169 | + nonlocal idx |
| 170 | + left = parse_factor() |
| 171 | + |
| 172 | + while idx < len(tokens) and tokens[idx] == '*': |
| 173 | + idx += 1 |
| 174 | + right = parse_factor() |
| 175 | + left = left * right |
| 176 | + |
| 177 | + return left |
| 178 | + |
| 179 | + def parse_factor(): |
| 180 | + """解析因子(数字、变量或括号表达式)""" |
| 181 | + nonlocal idx |
| 182 | + token = tokens[idx] |
| 183 | + idx += 1 |
| 184 | + |
| 185 | + if token == '(': |
| 186 | + result = parse() |
| 187 | + idx += 1 # 跳过 ')' |
| 188 | + return result |
| 189 | + elif token.lstrip('-').isdigit(): |
| 190 | + # 数字 |
| 191 | + return Poly({(): int(token)}) |
| 192 | + else: |
| 193 | + # 变量 |
| 194 | + if token in eval_map: |
| 195 | + return Poly({(): eval_map[token]}) |
| 196 | + else: |
| 197 | + return Poly({(token,): 1}) |
| 198 | + |
| 199 | + idx = 0 |
| 200 | + poly = parse() |
| 201 | + return poly.to_list() |
| 202 | +``` |
| 203 | + |
| 204 | +### 思路 1:复杂度分析 |
| 205 | + |
| 206 | +- **时间复杂度**:$O(n \times m)$,其中 $n$ 是表达式的长度,$m$ 是多项式项的数量。解析和计算都需要遍历表达式。 |
| 207 | +- **空间复杂度**:$O(m)$,需要存储多项式的所有项。 |
0 commit comments