括号序列-蓝桥杯
题面:
这是一道很巧妙的利用动态规划的题目。简单来说,就是往一个不完全匹配的括号序列中加括号,使得括号匹配,且要求添加的括号尽量少。
1. 思路分析:
-
在所有方案中,添加的括号数相等。因为所有添加括号的方案其实都是把不平衡的括号补齐,所以缺多少就可以补多少。这一点比较抽象,下文会提到。
-
在添加左括号时,要使添加的括号不重复,只能在')'之前添加。
这一点很好理解。因为我们可以以')'为分界符,将括号序列划分为很多段,如 ...)...)...)..., 其中的...表示0个或多个左括号。这样,我们每添加一个左括号,就是在一个...内又新增了括号,因为所有左括号都一样,所以具体添加到...(左括号堆)的位置不重要,添加的左括号数量才重要,可添加的数量数就是我们的方案数。
举一个例子,方便理解。比如说,((())))()))(())),我们在第一堆左括号中添加一个左括号,变成(((())))()))(())),可见,无论在第一堆左括号的哪个位置加(,最终的结果都一样。
因此,我们只需要在)之前集中加左括号就行了。
- 添加右括号的方案数,可以通过将原来的序列翻转,然后'('变')', ')'变'(',然后求加左括号的方案数就行了。最终的结果就是加左括号的方案数与加右括号的方案数相乘。
这个理解起来更抽象。首先,问一个问题,一个残缺的括号序列,是否需要同时加左括号或者右括号?
你可能以为跑遍整个序列,看看左括号多右括号多少,或者右括号多左括号多少,然后补上缺的部分就行了,可以只补充左括号或者右括号。非也!因为括号序列的有些地方前后不是一个整体(我们定义一个整体为只在整体内添加一种括号即可让括号平衡),会出现前面缺左括号,后面缺右括号的情况。
我们可以把序列中连续的部分分为3类,即(), )), ((, )(. 连续的部分一定在一个整体内,对于(), 可以肯定,他们一定是配对的,可以认为属于一个整体部分,重点是 )(。当)(前面缺右括号,后面缺左括号时,前面多的左括号正好可以与后面多的右括号配对,)(组合起来只会缺左右括号中的一种;而当 )( 前面的序列缺左括号,右面的序列缺右括号时,左面多的右括号并不会与后面的左括号抵消,所以两边需要分别加左右括号。
由此,我们还可以推导出整个括号序列要么只有一个整体,要么是两个整体,前面缺左括号,后面缺右括号。(想一想,为什么?)
举个例子,(()))()))(((() 按照 ')(' 的分隔分开,即为 (())) ())) (((()。示例括号序列的第一组缺左括号,第二组缺左括号,第三组缺右括号,所以一、二组可以连为一个整体,统一加左括号,由于最终目的是使括号序列平衡,故所有方案中加左括号的数目相等且最优。 (解释上面的1.)第三组构成一个整体,只需要加右括号,且行为不影响左面的整体。因此,添加左括号和右括号的行为相互独立,依照乘法原理,方案数相乘。
2. 动规方案
定义 dp[i][j] 为当扫描到第i个字符时,左括号多右括号j个时(添加后的结果),添加左括号的方案数。
定义 a[i] 为我们的原括号序列。
- 当 a[i] == '('时,我们不能加括号,则 dp[i][j] = dp[i-1][j-1].
- 当 a[i] == ')'时,我们可以加任意多的括号时序列偏向我们想要的方向。当上一个字符左括号比右括号多j+1个时,我们添加0个;多j个时,添加1个;多j-1个时,添加2个;... ;多0个时,添加j+1个。添加的方案数都是1种,故有:$ dp[i][j] = \Sigma_{k=0}^{j+1}{dp[i-1][k]} $
最后,我们让i从0开始遍历dp[n][i],得到第一个非0的数,即为添加左括号的方案数。
以()))(((为例,最后我们只能在缺左括号的整体添加左括号使其平衡,缺右括号(多左括号)的整体最好的方案就是不加左括号,因此,最后我们能达到最好的方案(指让能平衡的地方尽量平衡的方案),其左比右多的数目,即为第一个非0数的位置。
之后,按同样方案计算添加右括号方案数,二者相乘即可。