今天碰到这样一个问题:
n对括号正确匹配组成的字符串数,例如
1对括号:() 1种可能
2对括号:()() 1种可能
3对括号:((())) ()(()) ()()() (())() (()()) 5种可能
那么问题:n对括号有多少种正确配对的可能?
网上搜索了下,原来是卡特兰数问题。
百度百科解释:卡特兰数又称卡塔兰数,英文名Catalan number,是组合数学中一个常出现在各种计数问题中出现的数列。由以比利时的数学家欧仁·查理·卡塔兰 (1814–1894)命名,其前几项为 : 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, …
公式:卡特兰数有固定的解法公式,以后碰到可以直接用公式来求解。(以下这4种计算等价)
凸多边形三角划分:在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形。任务是键盘上输入凸多边形的边数n,求不同划分的方案数f(n)。比如当n=6时,f(6)=14。
(4)道路选择问题:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果她从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
(5)长度为 2n的 Dyck words的数量。 Dyck words是由 n个 X和 n个 Y组成的字符串,并且从左往右数, Y的数量不超过 X。
(7)圆桌握手问题: 圆桌周围有 2n个人,他们两两握手,但没有交叉的方案数。
(8)本文的“n对括号正确匹配组成的字符串数”问题。
虽然同属于卡特兰数问题,但是用同样的方法解释似乎行不通(看百度百科上的解释:关于出栈的问题。还是比较容易理解的,但是我却不能通过它理解本题)
最后发现是《编程之美2》这本书上的原题。其解释如下:
分析
“卡特兰数”除了可以使用公式计算,也可以采用“分级排列法”来求解。以 n对括弧的合法匹配为例,对于一个序列 (()而言,有两个左括弧,和一个右括弧,可以看成“抵消了一对括弧,还剩下一个左括弧等待抵消”,那么说明还可以在末尾增加一个右括弧,或者一个左括弧,没有左括弧剩余的时候,不能添加右括弧。
由此,问题可以理解为,总共 2n个括弧,求 1~2n级的情况,第 i 级保存所有剩余 i 个左括号的排列方案数。 1~8级的计算过程如下表:
(这里,我搞了很久才折腾明白,行1-8表示级数–括弧个数,列0-8表示不完整匹配左括号个数。红色为博主解释,其他为原文)
比如:1级: 只有一个,只能是左括号(。右括号不能先出现啊。
不完整左括号个数为 1个。 (
2级:由1级的左括号(,如果给它右括号,则完整匹配;此时不完整匹配左括号个数为0,因此在0这一列下为1;
如果给再给它左括号,则不完整匹配左括号个数为2,只有一种情况: (( 个数为1
不完整匹配左括号个数为1这种情况不存在!!!因此不填。
3级:一定是不完整的,所以不完整匹配左括号个数为0 的下方 空缺;
由2级可知,原来有完整匹配 1个”()”, 现在又增加了1个括弧,那么只能又出现了一个不完整匹配左括号
原来有不完整匹配左括号2个“((“,现在如果增加一个右括号,则不完整匹配左括号个数为1的情况+1
如果增加一个左括号,则不完整匹配左括号个数为3的情况+1
现在明白为什么下一级的个数为上一级相邻两边相加。比如3级 1列(0列起始)下=2级0列+2级2列。
依次类推:
4级:原来3级中有2个不完整1次括弧,那么4级中就有 完整匹配 2次。
4级中不完整2次括弧=3级的不完整1次+3级的不完整3次。
4级的不完整4次括弧 只能是”((((“全左括号,所以每行最后一个数字为1,表示全左括号。
over
计算过程解释如下: 1级:只能放 1个“(”; 2级:可以在一级末尾增加一个“)”或者一个“ (”
以后每级计算时,如果遇到剩余 n>0个“(”的方案,可以在末尾增加一个“ (”或者“ )”进入下一级;遇到剩余 n=0个“(”的方案,可以在末尾增加一个“ (”进入下一级。
奇数级只能包含剩余奇数个“(”的排列方案
偶数级只能包含剩余偶数个“(”的排列方案
从表中可以看出,灰色部分可以不用计算。
解法
关键代码为:
double Catalan(int n) { if (n == 0) return 1; for (int i = 2; i <= 2 * n; i++) { var m = i <= n ? i : 2 * n + 1 - i; for (int j = (i - 1) & 1; j <= m; j += 2) { if (j > 0) arr[j - 1] += arr[j]; if (j < n) arr[j + 1] += arr[j]; arr[j] = 0; } } return arr[0]; }
其中:
n为 Cn中的 n;
arr = new double[n + 1];//arr[i]代表有 k个括弧的时候,剩余 “(“个数为 i的排列方案个数 arr[1] = 1;
讨论
算法复杂度为 = O(n^2),空间复杂度为 O(n+1)。相对于利用公式计算而言,此方法的优势在于——没有乘除法,只有加法。