Catalan number卡特兰数

背景知识介绍:

  卡特兰数是离散数学中的一个重要数列,是很多生活场景的一个抽象,比如买早餐、买电影票等等。在很多大公司的笔试或者面试题中也常涉及到。

百度百科介绍

  卡特兰数

  卡特兰数是组合数学中一个常出现在各种计数问题中的数列。前20项为其前几项为 : 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420,…

  卡特兰数Cn满足以下递推关系:

( n − 3 ) C n = n 2 ( C 3 C n − 1 + C 4 C n − 2 + C 5 C n − 3 + ⋯ + C n − 2 C 4 + C n − 1 C 3 ) \left(n-3\right)C_n = \frac{n}{2}\left(C_3C_{n-1}+C_4C_{n-2}+C_5C_{n-3}+\cdots+C_{n-2}C_4+C_{n-1}C_3\right) (n3)Cn=2n(C3Cn1+C4Cn2+C5Cn3++Cn2C4+Cn1C3)

C n + 1 = C 2 C n + C 3 C n − 1 + ⋯ + C n C 2 C_{n+1}=C_2C_n+C_3C_{n-1}+\cdots+C_nC_2 Cn+1=C2Cn+C3Cn1++CnC2

  令h(0)=1,h(1)=1,catalan数满足递推式

h ( n ) = h ( 0 ) ∗ h ( n − 1 ) + h ( 1 ) ∗ h ( n − 2 ) + . . . + h ( n − 1 ) ∗ h ( 0 ) ( n > = 2 ) h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)*h(0) (n>=2) h(n)=h(0)h(n1)+h(1)h(n2)+...+h(n1)h(0)(n>=2)

  另类递推式:

h ( n ) = h ( n − 1 ) ∗ ( 4 ∗ n − 2 ) / ( n + 1 ) h(n)=h(n-1)*(4*n-2)/(n+1) h(n)=h(n1)(4n2)/(n+1)

wikipedia介绍

  翻译自wikipedia:Catalan number原文介绍

  第n项满足:

C n = C ( 2 n , n ) / ( n + 1 ) = C ( 2 n , n ) − C ( 2 n , n − 1 ) = ( 2 n ) ! / ( ( n + 1 ) ! ∗ n ! ) = ∏ k = 2 n n + k k C_n = C(2n,n)/(n+1) = C(2n,n) - C(2n,n-1)= {(2n)!}/{((n+1)!*n!)} = \prod_{k=2}^n\frac{n+k}{k} Cn=C(2n,n)/(n+1)=C(2n,n)C(2n,n1)=(2n)!/((n+1)!n!)=k=2nkn+k

  增长趋势:

C n ≈ 4 n n 3 / 2 ∗ π C_n \approx \frac{4^n}{{n^{3/2}*\sqrt{\pi}}} Cnn3/2π 4n

代码实现:

公式:

public static int getCatalanNumber(int n) {
        if (n <= 1)
            return 1;
        long res = 1;
        for (int k = n + 1; k <= 2 * n; k++)
            res = res * k / (k - n);
        return (int)(res / (n + 1));
}

递归:

public static long recur(int n) {
        if (n <= 1)
            return 1;
        long res;
        res = recur(n - 1) * (4 * n - 2);
        return (res / (n + 1));
}

DP思路:

class Solution {
    public int getCatalanNumber(int n) {
        if (n <= 1)
            return 1;
        int dp[] = new int[n + 1];
        dp[0] = dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++)
                dp[i] += dp[i - j] * dp[j - 1];
        }
        return dp[n];
    }
}

应用及分析

括号表达式

  矩阵连乘:P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?

  答案:f(n)=h(n-1)

  分析:可以这样考虑,首先通过括号化,将P分成两个部分,然后分别对两个部分进行括号化。比如分成(a1)×(a2×a3…×an),然后再对(a1)和(a2×a3…×an)分别括号化;又如分成(a1×a2)×(a3…×an),然后再对(a1×a2)和(a3…×an)括号化。设n个矩阵的括号化方案的种数为f(n),那么问题的解为

f ( n ) = f ( 1 ) ∗ f ( n − 1 ) + f ( 2 ) ∗ f ( n − 2 ) + f ( 3 ) ∗ f ( n − 3 ) + f ( n − 1 ) ∗ f ( 1 ) f(n) = f(1)*f(n-1) + f(2)*f(n-2) + f(3)*f(n-3) + f(n-1)*f(1) f(n)=f(1)f(n1)+f(2)f(n2)+f(3)f(n3)+f(n1)f(1)

f(1)*f(n-1)表示分成(a1)×(a2×a3…×an)两部分,然后分别括号化。计算开始几项,f(1) = 1, f(2) = 1, f(3) = 2,f(4)=5。结合递归式,不难发现 f(n)等于h(n-1)

出栈次序

  一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?

  答案:f(n)=h(n)

  分析:首先,我们设f(n)是序列个数为n的出栈序列种数。(我们假定,最后出栈的元素为k,显然,k取不同值时的情况是相互独立的,也就是求出每种k最后出栈的情况数后可用加法原则),由于k最后出栈,因此,在k入栈之前,比k小的值均出栈,此处情况有f(k-1)种;而之后比k大的值入栈,且都在k之前出栈,因此有f(n-k)种方式,由于比k小和比k大的值入栈出栈情况是相互独立的,此处可用乘法原则,f(n-k)*f(k-1)种,求和便是Catalan递归式。而k可以选1到n,所以再根据加法原理,将k取不同值的序列种数相加,得到的总序列种数为:f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0)。看到此处,再看看卡特兰数的递推式,答案不言而喻,即为f(n)=h(n)= C(2n,n)/(n+1)= c(2n,n)-c(2n,n-1)(n=0,1,2,……)。最后,令f(0)=1,f(1)=1。

凸多边形三角划分

  在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形。任务是键盘上输入凸多边形的边数n,求不同划分的方案数f(n)。比如当n=6时,f(6)=14。

  Catalan number卡特兰数_第1张图片

  答案:f(n)= h(n-2)

  分析:因为凸多边形的任意一条边必定属于某一个三角形,所以我们以某一条边为基准,以这条边的两个顶点为起点P1和终点Pn(P即Point),将该凸多边形的顶点依序标记为P1、P2、……、Pn,再在该凸多边形中找任意一个不属于这两个点的顶点Pk(2<=k<=n-1),来构成一个三角形,用这个三角形把一个凸多边形划分成两个凸多边形,其中一个凸多边形是由P1,P2,……,Pk构成的凸k边形(顶点数即是边数),另一个凸多边形是由Pk,Pk+1,……,Pn构成的凸n-k+1边形。

  此时,我们若把Pk视为确定一点,那么根据乘法原理,f(n)的问题就等价于——凸k多边形的划分方案数乘以凸n-k+1多边形的划分方案数,即选择Pk这个顶点的f(n)=f(k)×f(n-k+1)。而k可以选2到n-1,所以再根据加法原理,将k取不同值的划分方案相加,得到的总方案数为:f(n)=f(2)f(n-2+1)+f(3)f(n-3+1)+……+f(n-1)f(2)。看到此处,再看看卡特兰数的递推式,答案不言而喻,即为f(n)=h(n-2)(n=2,3,4,……)。此处f(2)=1和f(3)=1的具体缘由须参考详尽的“卡特兰数”。

类似问题一:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?

  答案:f(2n)= h(n)

  分析:以其中一个点为基点,编号为0,然后按顺时针方向将其他点依次编号。那么与编号为0相连点的编号一定是奇数,否则,这两个编号间含有奇数个点,势必会有个点被孤立,即在一条线段的两侧分别有一个孤立点,从而导致两线段相交。设选中的基点为A,与它连接的点为B,那么A和B将所有点分成两个部分,一部分位于A、B的左边,另一部分位于A、B的右边。然后分别对这两部分求解即可。
设问题的解f(n),那么

f ( n ) = f ( 0 ) ∗ f ( n − 2 ) + f ( 2 ) ∗ f ( n − 4 ) + f ( 4 ) ∗ f ( n − 6 ) + . . . . . . f ( n − 4 ) ∗ f ( 2 ) + f ( n − 2 ) ∗ f ( 0 ) f(n) = f(0)*f(n-2) + f(2)*f(n-4) + f(4)*f(n-6) + ......f(n-4)*f(2) + f(n-2)*f(0) f(n)=f(0)f(n2)+f(2)f(n4)+f(4)f(n6)+......f(n4)f(2)+f(n2)f(0)

  f(0)*f(n-2)表示编号0的点与编号1的点相连,此时位于它们右边的点的个数为0,而位于它们左边的点为2n-2。依次类推,f(0) = 1, f(2) = 1, f(4) = 2。结合递归式,不难发现f(2n) 等于h(n)。

类似问题二:Cn=n*n的方格地图中,从一个角到另外一个角,不跨越对角线的路径数?例如,4×4方格地图中的路径有14条:

  Catalan number卡特兰数_第2张图片

  答案:f(2n)= h(n)

类似问题三:圆桌周围有2n个人,他们两两握手,但没有交叉的方案数?

  答案:f(2n)= h(n)

二叉搜索树个数

  给定N个节点,能构成多少种不同的二叉搜索树?

  答案:f(n)= h(n)

  分析:To build a tree contains {1,2,3,4,5}. First we pick 1 as root, for the left sub tree, there are none; for the right sub tree, we need count how many possible trees are there constructed from {2,3,4,5}, apparently it’s the same number as {1,2,3,4}. So the total number of trees under “1” picked as root is dp[0] * dp[4] = 14. (assume dp[0] =1). Similarly, root 2 has dp[1]*dp[3] = 5 trees. root 3 has dp[2]*dp[2] = 4, root 4 has dp[3]*dp[1]= 5 and root 5 has dp[0]*dp[4] = 14. Finally sum the up and it’s done.

括号匹配

给定n对括号,求括号正确配对的字符串数,例如:

  • 0对括号:[空序列] 1种可能
  • 1对括号:() 1种可能
  • 2对括号:()() (()) 2种可能
  • 3对括号:((())) ()(()) ()()() (())() (()()) 5种可能

那么问题来了,n对括号有多少种正确配对的可能呢?

  答案:f(n)= h(n)

(阿里)买烧饼、电影票之类

  说16(2n)个人按顺序去买烧饼,其中8个人每人身上只有一张5块钱,另外8个人每人身上只有一张10块钱。烧饼5块一个,开始时烧饼店老板身上没有钱。16个顾客互相不通气,每人只买一个。问这16个人共有多少种排列方法能避免找不开钱的情况出现?

  **答案:f(2n)= h(n)n!n!

  分析:不难看出,该题求的是左端点为首元素的任意区间内,5的个数大于等于10的个数。因为每個人都是不一样的,所以需要全排列 (*n!*n!)。

  可以推广到一般形式,1元的m人,2元的n人。

   ( C ( m + n , n ) − C ( m + n , m + 1 ) ) ∗ m ! ∗ n ! = ( C ( m + n , n ) − C ( m + n , n − 1 ) ) ∗ m ! ∗ n ! ( C(m+n,n) - C(m+n,m+1) ) * m! * n!= ( C(m+n,n) - C(m+n,n-1) ) * m! * n! (C(m+n,n)C(m+n,m+1))m!n!=(C(m+n,n)C(m+n,n1))m!n!

类似问题:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(可以将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)

  **答案:f(n)= h(n)n!n!

  分析:将问题转化为:带5块钱的排前面的个数总是要大于带10块钱的人的个数,即C(16,8)-C(16,7)

(腾讯)借书还书

  在图书馆一共6个人在排队,3个在还《面试宝典》一书,3个在借《面试宝典》一书,图书馆此时没有《面试宝典》了,求他们排队的总数?

  答案:f(2n)= h(n)*n!*n!

  分析:将问题转化为:还书的人总是要大于或等于借书的人,即C(6,3)-C(6,2)

1和-1排列问题

  有n个1和m个-1(n>m),共n+m个数排成一列,满足对所有0<=k<=n+m的前k个数的部分和Sk>0的排列数。 问题等价为在一个格点阵列中,从(0,0)点走到(n,m)点且不经过对角线x==y的方法数(x > y)?

  答案:f(n)= h(n)

  分析:将问题转化一下,就是加括号的方法数,排1的个数一定要大于或者等于排的-1个数。

乒乓球得分问题

  甲乙两人比赛乒乓球,最后结果为20∶20,问比赛过程中甲始终领先乙的计分情形的种数?即甲在得到1分到19分的过程中始终领先乙。

  答案:f(n)= h(n),n=19

高矮排队问题

  12(2n)个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应的第一排的人高,问排列方式有多少种?

  答案:f(n)= h(n)

叶子结点求二叉树

  拥有n+1个叶子节点的二叉树的种类有多少?

  答案:f(n)= h(n)

  例如:4个叶子节点的所有二叉树形态

  image

格子填数

  在一个2*n的格子中填入1到2n这些数值使得每个格子内的数值都比其右边和上边的所有数值都小的方法有多少?

  答案:f(n)= h(n)

集合不交叉划分

  集合{1,2,…,2n}的不交叉划分的方法是多少?

  答案:f(2n)= h(n)

  分析:这里解释一下不交叉划分,我们对于集合{a,b}和{c,d},假设他们组成了两个区间[a,b]和[c,d],我们假设两个区间不重合,那么以下四种情况当做是不交叉的:

   a < c < d < b , a < b < c < d , c < a < b < d , c < d < a < b a<c<d<b,a<b<c<d,c<a<b<d,c<d<a<b a<c<d<b,a<b<c<d,c<a<b<d,c<d<a<b

  就是说两个区间可以包含或者相离,那么此时我们称集合{a,b}和{c,d}是不交叉的。对于集合{1,2,…,2n},将里面元素两两分为一子集,共n个,若任意两个子集都是不交叉的,那么我们称此时的这个划分为一个不交叉划分。此时不交叉的划分数就是我们的h(n)了.证明也很容易,我们将每个子集中较小的数用左括号代替,较大的用右括号代替,那么带入原来的1至2n的序列中就形成了合法括号问题。例如我们的集合{1,2,3,4,5,6}的不交叉划分有五个:{{1,2},{3,4},{5,6}},{{1,2},{3,6},{4,5}},{{1,4},{2,3},{5,6}},{{1,6},{2,3},{4,5}}和{{1,6},{2,5},{3,4}}。

——乐于分享,共同进步,欢迎补充
——Any comments greatly appreciated
——诚心欢迎各位交流讨论!QQ:1138517609
——CSDN:https://blog.csdn.net/u011489043
——简书:https://www.jianshu.com/u/4968682d58d1
——GitHub:https://github.com/selfconzrr

你可能感兴趣的:(编程算法,编程题整理)