|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[1106. 解析布尔表达式](https://leetcode.cn/problems/parsing-a-boolean-expression/solution/by-ac_oier-jr29/)** ,难度为**困难**。 |
| 4 | + |
| 5 | +Tag : 「栈」、「表达式计算」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +给你一个以字符串形式表述的 布尔表达式 `(boolean) expression`,返回该式的运算结果。 |
| 10 | + |
| 11 | +有效的表达式需遵循以下约定: |
| 12 | + |
| 13 | +* `"t"`,运算结果为 `True` |
| 14 | +* `"f"`,运算结果为 `False` |
| 15 | +* `"!(expr)"`,运算过程为对内部表达式 `expr` 进行逻辑 非的运算(`NOT`) |
| 16 | +* `"&(expr1,expr2,...)"`,运算过程为对 `2` 个或以上内部表达式 `expr1, expr2, ...` 进行逻辑 与的运算(`AND`) |
| 17 | +* `"|(expr1,expr2,...)"`,运算过程为对 `2` 个或以上内部表达式 `expr1, expr2, ...` 进行逻辑 或的运算(`OR`) |
| 18 | + |
| 19 | + |
| 20 | +示例 1: |
| 21 | +``` |
| 22 | +输入:expression = "!(f)" |
| 23 | + |
| 24 | +输出:true |
| 25 | +``` |
| 26 | +示例 2: |
| 27 | +``` |
| 28 | +输入:expression = "|(f,t)" |
| 29 | + |
| 30 | +输出:true |
| 31 | +``` |
| 32 | +示例 3: |
| 33 | +``` |
| 34 | +输入:expression = "&(t,f)" |
| 35 | + |
| 36 | +输出:false |
| 37 | +``` |
| 38 | +示例 4: |
| 39 | +``` |
| 40 | +输入:expression = "|(&(t,f,t),!(t))" |
| 41 | + |
| 42 | +输出:false |
| 43 | +``` |
| 44 | + |
| 45 | +提示: |
| 46 | +* 1ドル <= expression.length <= 20000$ |
| 47 | +* `expression[i]` 由 `{'(', ')', '&', '|', '!', 't', 'f', ','}` 中的字符组成。 |
| 48 | +* `expression` 是以上述形式给出的有效表达式,表示一个布尔值。 |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +### 双栈 |
| 53 | + |
| 54 | +为了方便,我们令 `expression` 为 `s`。 |
| 55 | + |
| 56 | +我们可以将 `t` 和 `f` 看做操作数,而 `|`、`&` 和 `!` 看做操作符,创建两个栈 `nums` 和 `ops` 分别对其进行存储。 |
| 57 | + |
| 58 | +剩余的 `()` 和 `,` 则只是优先级和分隔符,无须额外关注。 |
| 59 | + |
| 60 | +从前往后处理 `s`,根据当前处理的字符为何值进行分情况讨论: |
| 61 | + |
| 62 | +* `,`:分隔符,直接跳过; |
| 63 | +* `t` 或 `f`:操作数,添加到 `nums` 栈中; |
| 64 | +* `|`、`&` 或 `!`:操作符,添加到 `ops` 栈中; |
| 65 | +* `(`:子表达式的左端点,为了在我们从「双栈」中取出数值和符号计算时,可以知道某个子表达式计算完成,需要记录一下。往 `nums` 追加一个占位符号 `-` 来代指; |
| 66 | +* `)`:子表达式的右端点,代表一个子表达式的结束。可从「双栈」中取出符号和数组进行计算(在 `ops` 中仅取栈顶元素,代表当前子表达式的操作符;而在 `nums` 中则取到代表左端点的占位元素 `-` 为止),并将结果重新放入 `nums` 中。 |
| 67 | + |
| 68 | +最后考虑如何计算最简表达式,考虑实现一个 `char calc(char a, char b, char op)` 函数,代表对操作数 `a` 和 `b` 执行 `op` 操作并进行结果返回。 |
| 69 | + |
| 70 | +实际上,在 `calc` 函数我们只区分 `|` 操作和其他操作即可。也就是说 `&` 和 `!` 均当做 `&` 来做,`!` 操作在计算完整个表达式后再翻转。 |
| 71 | + |
| 72 | +Java 代码: |
| 73 | +```Java |
| 74 | +class Solution { |
| 75 | + public boolean parseBoolExpr(String s) { |
| 76 | + Deque<Character> nums = new ArrayDeque<>(), ops = new ArrayDeque<>(); |
| 77 | + for (char c : s.toCharArray()) { |
| 78 | + if (c == ',') continue; |
| 79 | + if (c == 't' || c == 'f') nums.addLast(c); |
| 80 | + if (c == '|' || c == '&' || c == '!') ops.addLast(c); |
| 81 | + if (c == '(') nums.addLast('-'); |
| 82 | + if (c == ')') { |
| 83 | + char op = ops.pollLast(), cur = ' '; |
| 84 | + while (!nums.isEmpty() && nums.peekLast() != '-') { |
| 85 | + char top = nums.pollLast(); |
| 86 | + cur = cur == ' ' ? top : calc(top, cur, op); |
| 87 | + } |
| 88 | + if (op == '!') cur = cur == 't' ? 'f' : 't'; |
| 89 | + nums.pollLast(); nums.addLast(cur); |
| 90 | + } |
| 91 | + } |
| 92 | + return nums.peekLast() == 't'; |
| 93 | + } |
| 94 | + char calc(char a, char b, char op) { |
| 95 | + boolean x = a == 't', y = b == 't'; |
| 96 | + boolean ans = op == '|' ? x | y : x & y; |
| 97 | + return ans ? 't' : 'f'; |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | +TypeScript 代码: |
| 102 | +```TypeScript |
| 103 | +function parseBoolExpr(s: string): boolean { |
| 104 | + function calc(a: string, b: string, op: string): string { |
| 105 | + const x = a == 't', y = b == 't' |
| 106 | + const ans = op == '|' ? x || y : x && y |
| 107 | + return ans ? 't' : 'f' |
| 108 | + } |
| 109 | + const nums = new Array<string>(s.length).fill(''), ops = new Array<string>(s.length).fill('') |
| 110 | + let idx1 = 0, idx2 = 0 |
| 111 | + for (const c of s) { |
| 112 | + if (c == ',') continue |
| 113 | + if (c == 't' || c == 'f') nums[idx1++] = c |
| 114 | + if (c == '|' || c == '&' || c == '!') ops[idx2++] = c |
| 115 | + if (c == '(') nums[idx1++] = '-' |
| 116 | + if (c == ')') { |
| 117 | + let op = ops[--idx2], cur = ' ' |
| 118 | + while (idx1 > 0 && nums[idx1 - 1] != '-') { |
| 119 | + const top = nums[--idx1] |
| 120 | + cur = cur == ' ' ? top : calc(top, cur, op) |
| 121 | + } |
| 122 | + if (op == '!') cur = cur == 't' ? 'f' : 't' |
| 123 | + idx1--; nums[idx1++] = cur |
| 124 | + } |
| 125 | + } |
| 126 | + return nums[idx1 - 1] == 't' |
| 127 | +} |
| 128 | +``` |
| 129 | +Python 代码: |
| 130 | +```Python |
| 131 | +class Solution: |
| 132 | + def parseBoolExpr(self, s: str) -> bool: |
| 133 | + def calc(a, b, op): |
| 134 | + x, y = a == 't', b == 't' |
| 135 | + ans = x | y if op == '|' else x & y |
| 136 | + return 't' if ans else 'f' |
| 137 | + nums, ops = [], [] |
| 138 | + for c in s: |
| 139 | + if c == ',': |
| 140 | + continue |
| 141 | + if c == 't' or c == 'f': |
| 142 | + nums.append(c) |
| 143 | + if c == '|' or c == '&' or c == '!': |
| 144 | + ops.append(c) |
| 145 | + if c == '(': |
| 146 | + nums.append('-') |
| 147 | + if c == ')': |
| 148 | + op, cur = ops.pop(), ' ' |
| 149 | + while nums and nums[-1] != '-': |
| 150 | + top = nums.pop() |
| 151 | + cur = top if cur == ' ' else calc(cur, top, op) |
| 152 | + if op == '!': |
| 153 | + cur = 't' if cur == 'f' else 'f' |
| 154 | + nums.pop() |
| 155 | + nums.append(cur) |
| 156 | + return nums[-1] == 't' |
| 157 | +``` |
| 158 | +* 时间复杂度:$O(n)$ |
| 159 | +* 空间复杂度:$O(n)$ |
| 160 | + |
| 161 | +--- |
| 162 | + |
| 163 | +### 最后 |
| 164 | + |
| 165 | +这是我们「刷穿 LeetCode」系列文章的第 `No.1106` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 166 | + |
| 167 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 168 | + |
| 169 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 170 | + |
| 171 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 172 | + |
0 commit comments