题目描述
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; |