题目描述
Given a string that contains only digits 0-9 and a target value, return all possibilities to add binary operators (not unary) +, -, or * between the digits so they evaluate to the target value.
1 | Examples: |
Credits: Special thanks to @davidtan1890 for adding this problem and creating all test cases.
分析
枚举所有位置的符号(并置、+
、-
、*
)后转化为表达式求值。加速的办法是让枚举和求值一起进行。
考虑题目要求的文法: 1
2
3
4E ::= E "+" F | E "-" F
F ::= F "*" L
L ::= "0" | L1
L1 ::= "1" | ... | "9" | L1 "0" | ... | L1 "9"
使用不考虑前导0的简化文法。我们会采用递归算法,形似recursive descent
parser,在递归过程中不难采取一些措施避免生成前导0。 1
2
3E ::= E "+" F | E "-" F
F ::= F "*" L
L ::= "0" | ... | "9" | L "0" | ... | L "9"
Parse
tree以0
~9
为叶子,+
、-
、并置(下面用@
表示)为内部节点,其最右节点到根的距离小于等于3,之后会说明原因。对于某一前缀的parse
tree,添加一个符号和一个数字后会得到一棵新的parse
tree,这一过程可以看作在二叉树中添加一中序遍历序号最大的节点(类似于Cartesian
tree线性构造算法)。取决于添加的符号,可能有以下三种变化。最左边为原先的parse
tree,后面三棵为添加不同符号后得到的parse tree: 1
2
3
4
5
6
7
8
9 + + + +
/ \ / \ / \ / \
2 * 2 * 2 * + 6
/ \ / \ / \ / \
3 @ 3 @ * 6 2 *
/ \ / \ / \ / \
4 5 @ 6 3 @ 3 @
/ \ / \ / \
4 5 4 5 4 5
我们给parse tree每个节点设置semantic
value,即对应的表达式的结果。因为节点按照中序遍历顺序添加,任何左子树的结构不会再发生变化,可以把整棵子树收缩成一个节点。@
是文法优先级最高,也可以规约,即把对应子树收缩。上面的四棵树收缩后变成下面这样:
1
2
3
4
5 + + + +
/ \ / \ / \ / \
2 * 2 * 2 * 137 6
/ \ / \ / \
3 45 3 456 135 6
文法中的三个产生式有层级关系,高优先级符号的子树中没有低优先级符号,这就是任意节点到根距离小于等于3的原因。从另一个角度看,LR(0)项集是无圈的,最长路径长度为3。枚举并求parse tree的过程很像SLR(1),又像Earley parser。
-
可以看作+
,同时把右边的数字视为相反数。这样,我们得到的tree只剩下+
*
两种操作符。+
、*
都有单位元,若最右路径没有*
,可以补上一个左孩子为1的节点;若没有+
,可以补上一个左孩子为0的节点。这样保证了最右节点到根的距离恒为2,且树中有三个叶子,三个semantic
values。
以上解释了三个semantic
values可以刻画状态的原因。从左到右枚举各个位置上的符号,不难得到一个边枚举边求值的算法,枚举量为
注意相遇时要合并两边的部分结果。下面逐一考虑。
+
相遇比较容易。假设左边部分的和为
-
相遇类似。
@
相遇,合并时两边都得用到三个semantic values,@
相遇,放置并置符号后,交给递归调用的函数在之后相遇。若之后出现了+
或-
,那么相遇过程就延迟到那时进行;若没有出现,则在全式末尾进行。
*
相遇,若像@
那么不处理,考虑枚举量。设从右到左的有*
、@
两种符号都没处理相遇,还得考虑延迟相遇的情况数,为
处理*
相遇需要用到两个semantic
values,把左边部分在最后一个加号处断开(-
可以转化为+
),得到
于是问题转化为:给出*
相遇。更好的方法可能是把点和线批量处理。若
1 | typedef long long ll; |