括号与数学表达式的计算
往上加运算符也好,加括号也好,删除括号也好,最稳妥的方式都是 带 StringBuilder 从头扫的 DFS + Backtracking.
在没有任何优先级后顾之忧的情况下,可以以操作符为分界线,Divide & Conquer. 否则得存每个 subproblem 的前后缀用于做 join.
有乘法优先级顾虑时,需要在 dfs 参数里传上一次操作的值,用于恢复和重新计算;连续多个乘法可以自动叠加。
在只有一种括号的情况下,维护 left / right count 就可以 one-pass 知道到底有几个 left / right 括号没正确匹配
Dijkstra 的 Shunting-Yard 算法是生成 RPN 的大杀器,也是 calculator 类问题的通解。
而DFS 中想 cache 一个 List<>,远远不如 String 方便。


带记忆化搜索,3ms,超过 91.61%
注意这招只在只有一种括号的时候有效,不能用来做 "Valid Parentheses",会在 "([)]" 这种 test case 上挂掉。
Valid Parenthese 只有可能是这两种情况;
(FB) 简化版,Remove Invalid Parenthese
在只有一种括号的时候,是可以扫一遍通过 +/- 来找出非法括号的,不过以一个方向定义的 +/- 只能找出一种,需要两个方向都扫一遍。
这题的结构就和 Different ways to add parenthese 不一样,括号之间的相对位置是非常重要的,而且有连续性,不能像那题一样直接在操作符上建一个 expression tree 出来,divide & conquer.
只有一个非法括号的情况:
有两个非法括号的情况:
觉得顺着这个思路越想细节越多。。于是参考了下论坛的 DFS 思路,在所有 DFS 解法中,我最喜欢这个,非常简洁易懂,而且不依赖什么 trick,便于和面试官交流。
这种解法的主要优点是好理解,空间上因为只用了一个 StringBuilder 非常经济,相比 BFS 速度和空间上都优异很多。如果说进一步优化的空间在哪,那就是对于 “重复状态” 的缓存与记忆化搜素还有提高的空间。
于是很 naive 的尝试了下加入 Set<> 来记录已经访问过的 StringBuilder 状态试图剪枝,然而很容易就出了 “没有任何返回结果” 的 bug ,其原因是,这个 dfs + backtracking 的代码本来就是在一个神似 binary tree 的结构上做一个 dfs 穷举而已,并不是 top-down recursion 那种子问题覆盖的结构,所以不适合用记忆化搜索解决。非要用的话至少 dfs 的返回类型就不能是 void ,至少也得是个 List<> 或者 Set<> 之类的 “子问题结果” 嘛。
参考论坛思路的一个 BFS 写法,速度很慢只能超过 14%,搞的有点像 word ladder 了。。。 不过另一个好处是,既然这么高的时间复杂度都能过,我那个 dfs 的解法也不用太追求完美,这样 index offset 的问题更好解决,遇到个新 string 直接重扫就好了。
而这题里,用 divide & conquer 无可避免地遇到符号优先级问题。于是看了一圈 LC 论坛,大家的做法其实都是 DFS + backtracking ..

所以说,珍爱生命,有乘法就远离 divide & conquer 吧。
一个思路不错的帖子,这种做法就不用考虑 join了,只要做乘法的时候看它的前一个元素就行;
流程:
计算器类问题,离不开 Dijkstra 的 Shunting-yard algorithm 和 reverse polish notation.
Input: 3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3
复现了一下 Dijkstra 的 shunting yard algorithm,因为只有 “+” 和 “-” ,运算符优先级上要简单一些。在这个代码基础上扩展任何新的运算符都很容易,加个判断函数就行了。
Last updated