Catalan(卡特兰)数

二叉搜索树概念:

介绍卡特兰数之前先来了解一些二叉搜索树的概念。比如有一棵树,它根节点比左边节点要大,比右边节点要小,这样的树就称为二叉搜索树。如下图所示:

    

卡特兰数:

我们把n个节点所能组成的不同二叉搜索树的个数称为卡特兰数(Catalan数)。接下来我们来看一下不同的卡特兰数是怎么计算出来的。

卡特兰数分析:

我们把C(n)记为卡特兰数,当节点数为1时,只能组成一种二叉搜索树,因此C(1) = 1。

C(2)可以这样来分析: 我们可以把1作为根节点,那么2只能放在右边,只有一种, 把2作为根节点,那么1只能放在左边,也是只有一种。  因此C(2) = 2。

C(3):有3个节点,我们可以分别把 1 、 2、 3作为根节点(3种情况),当1作为根节点时,剩下2、3都比1大,全在右边,这时两个节点有两种二叉搜索树,也就是C(2)。当2作为根节点,如上图,只有一种情况。当3作为根节点(同1),也是C(2)。

因此C(3) = C(2) + C(1) + C(2) = 5。

4个根节点也一样分析,把1、2、3、4分别作为根节点分4种情况分析。

C(4) = C(3) + C(2) + C(2) + C(3) = 14。

因为5个节点组成的二叉搜索树太多,只准备了个简图。还是把1、2、3、4、5分别作为根节点分5种情况分析。以根节点1为例,C(0)表示左边没有节点,C(4)表示右边有4个节点,C(0)可以看成是一个特殊值,C(0) = 1。

最终的结果C(5) 就是把左边的卡特兰数和右边的卡特兰数相乘,然后再相加。

即:C(5) = C(0) * C(4) + C(1) * C(3) + C(2) * C(2) + C(3) * C(1) + C(4) * C(0)

= 1 * 14 + 1 * 5+ 2 * 2 + 5 * 1 + 14 * 1 = 42。

编写思想: 

先不要着急分析卡特兰数相乘和相加等细节,先想着怎么拆分成5种情况。分析出大概框架,我们可以看出,可能存在重复计算的情况,可以用动态规划思想,使用数组存储之前计算出的卡特兰数。

第5个卡特兰数可以拆成这样:

剩下的C(4)、C(3)...都需要拆分。  C(0)、 C(1) 已知,都为1。

代码实现:
import java.util.Arrays;

/**
 * 卡特兰数
 */
public class Catalan {

    public static void main(String[] args) {
        System.out.println(catalan(5));
    }

    public static int catalan(int n){
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for (int j = 2; j < n + 1; j++) { // 分5种情况  计算从2到5的所有卡特兰数,然后填充到相应的dp数组里
            for (int i = 0; i < j; i++) {  // 求第j个卡特兰数  只拆分了C(5)  还需要拆分其他的
                System.out.printf("(%d,%d)\t", i, j - 1 - i);
                //dp[j] += dp[i] * dp[j - 1 - i];
            }
            //System.out.println();
            //System.out.println(Arrays.toString(dp));
        }
        return dp[n];
    }
}
例题:

接下来我们来看一道Leetcode题目:

其实这道题目就是求解第n个卡特兰数

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

卡特兰数的应用:

接下来我们再来看一道类似的题目:

假如有n个元素的进栈顺序(序列)为:1,2,3,4,...,n,则有多少种出栈顺序(序列)?

例题分析:

我们可以这样来看,如果只有1个元素进栈,那么出栈顺序只有一种。

如果有两个元素进栈,1可以第一个出栈,也可以第2个出栈,出栈顺序有两种。分别是(1,2)(2,1)。

当有3个元素进栈时,我们可以分成3种情况:①1 第一个出栈   ②1 第二个出栈  ③1最后一个出栈。

如果1是第一个出栈,那么栈内还有两个元素,此时有两种出栈顺序(1,2,3)和(1,3,2),

如果1是第二个出栈,此时只有一种出栈顺序,为(2,1,3),

如果1是第三个出栈,此时和第一种情况一样,有两种出栈顺序(3,2,1)和(2,3,1)。

综上分析,一共有5种出栈顺序。

如果大家对数字比较敏感,这时我们很容易发现这就是一个卡特兰数。

接下来我们再来看有4个元素进栈的情况:

我们把这4种情况的左右两边相乘再累加就能得出最终结果14。

我们再来看一道例题:

例题2:

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。

题2分析:

当n = 1时,表示给出一对括号,此时组成有效括号的只有1种。

当n = 2时,可以分为两种情况:① 一对有效括号内不包含其它括号 ②一对有效括号内包含另一对其它括号。此时组成有效括号的有两种。

当n = 3时,有3种情况:

①一对括号不包含其它括号(和它平级的括号有两种)和剩余的两对括号组合。

②一对括号内只包含一对括号(和它平级的括号有一种)和剩余的一对括号组合。

③一对括号内包含其他两对括号(没有和它平级的括号)和剩余的0对括号组合。

这样一共有5种组合方式。如下图:

n = 4时,和上述分析一样:

还是左右两边相乘再相加得出最终结果。

本题和上述其他题目还不一样,本题要求列出所有的有效括号组合。

代码实现:
import java.util.ArrayList;
import java.util.List;

/**
 * 22. 括号生成
 */
public class Leetcode22 {

    public static List<String> generateParenthesis(int n) {
        //返回的是一个集合,里面装着不同的括号组合
        ArrayList<String>[] dp = new ArrayList[n + 1];
        dp[0] = new ArrayList<>(List.of("")); //""
        dp[1] = new ArrayList<>(List.of("()")); //()
        for (int j = 2; j < n + 1; j++) {
            dp[j] = new ArrayList<>();
            for (int i = 0; i < j; i++) { //求第j个卡特兰数
                //i 对应的集合是内层要嵌套的括号, j - 1 - i 对应的集合是平级要拼接的括号
                System.out.printf("(%d,%d)\t", i, n - 1 - i);
                //分析的永远是第一对括号()内的情况,可能嵌套其它括号
                for (String k1 : dp[i]) {  //""
                    for (String k2 : dp[j - 1 - i]) { // ()
                        dp[j].add("(" + k1 + ")" + k2);
                    }
                }
            }
            System.out.println();
        }

        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(generateParenthesis(3));
    }
}

这题用动态规划的解法可能效率不高,但是能通过。

类似的,再来看一道例题:

买卖找零问题:

售票处售卖球票,每张票 50 元。有2n人前来买票

  • 其中一半人手持 50 元钞票

  • 另一半人手持 100 元钞票

若售票处开始没有任何零钱,问:有多少种排队方式,能够让售票顺畅进行。

思路:

  • 把手持 50 元钞票的人视为左括号

  • 把手持 100 元钞票的人视为右括号

  • 左右括号合法配对,即先出现左括号,再出现右括号,就可以让售票顺畅执行

可以看到,问题又变成了求解 n 的卡特兰数

  • 22
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值