96-不同的二叉搜索树-美丽的卡特兰数

写在最前面

上题之前先写点前两天面试时面试官出的一道算法题的感想。他给的题目是实现一个,使得压栈、弹栈、求最小值的时间复杂度都是O(1),讲出思路即可。听到这道题我第一个反应就是刷过,印象里多用一个双向队列实现,然后在开始思考具体怎么讲,结果感觉怎么讲都实现不了就有点绕进去了,最后也没讲清楚,其实应该额外使用一个栈来存储较小的值。而我刷过的那题,实际上是要实现一个O(1)插入、删除、返回最大值的队列。两道题相似,但是实现的数据结构完全不一样,所以前面写这一段希望读者刷LeetCode题千万不要习惯性思维,不要背题解,重要的是学习思考的思路和方法,也算是一点小教训吧。正片开始

题目

核心思路

这也是一道比较典型的DP问题,如果从1到3把图画出来的话,很容易发现 n 对应的二叉搜索树是与 n - 1 等之前的数有关的,至于什么关系,我们通过图来发现他。

可以看到图中蓝字的1,2,3表示n = 1, 2, 3时的二叉搜索树,红字及红框圈起来的是重复的结构,如蓝字2中重复了1的结构所以 n = 2 时的答案为 1 + 1 = 2; 蓝字3中重复了2和1中的结构,故 n = 3 时的答案为 2 + 2 + 1 = 5。这里的2 2 1分别是以1、3、2为根节点所得到的二叉树的数量。综上,我们可以根据此来定义DP数组的含义。

定义及初始化

int[] dp = new int[n + 1];//dp[i] 表示i个结点能排列的二叉搜索树的数量

根据此定义,我们很容易想到dp[0] = 0、dp[1] = 1。然后就需要寻找状态转移方程(递推公式)。

状态转移方程

根据核心思路的分析,我们需要将 n 个结点分别作为跟根节点,然后根据子节点个数就可以找到之前计算过的值进行复用。直接看上图观察,1和3对应的种类直接等于2,因为他们只有一棵子树,而2有两棵子树,他就要用 左子树的数 * 右子树的数(根据排列组合思想),所以我们似乎可以得到这样的结论:

//i 及上文所说的 dp[i] 的下标
for(int j = 1; j <= i; j++){
  //j作为根节点
  if(j == 1 || j == i){
     dp[i] += dp[j - 1] + dp[i - j];
  }else{
     dp[i] += dp[j - 1] * dp[i - j];
  }
}

这样的结果便可以AC,不过可以发现if - else 部分之所以分支,是因为有 dp[0] = 0; 导致不得不把他分出去,但是他的值:0 + dp[i - j] (举例) 其实也可以变成 1 * dp[i - j] 只要我们令 dp[0] = 1 ,就可以把两个分支合并为一个,代码简洁了不少。

完整代码

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            for(int j = 1; j <= i; j++){
                //表示j作为根节点
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
}

虽然得到的结果很不错,但其实还有更简单的方法来解决。

卡特兰数(卡塔兰数)

这是个很神奇的数(或者说数列),它跟斐波那契数列同样出名,更具体的内容还请自行百度。其实上边计算的 dp[i] 就等于第i个卡特兰数,至于其原因我也并没有深究,如果感兴趣可以找些资料深入研究。给出卡特兰数的递推公式:

其实他还有另一个化简后只与 n 相关的公式,但是要使用到组合数求阶乘,并不比递推简单高效,所以直接使用递推公式来实现即可(ps:虽然我想到了卡特兰数,但是实在想不起来公式了所以上来就用的DP来做题)

代码

class Solution {
    public int numTrees(int n) {
        long c = 1;
        for(int i = 0; i < n; i++){
            c = c * 2 * (2 * i + 1) / (i + 2);
        }
        return (int)c;
    }
}

代码一下就简单了很多,复杂度也下降了一个级别降为O(N),也许这就是数学的魅力吧。另外说一下使用long的原因,虽然最终结果是在int的范围内,但是在 c 除以 (i + 2) 之前其值是可能越界的(第19个卡特兰数为1767263190),所以使用long来接收结果然后强转为int以防越界。如有不准确的地方还请指出,感谢相遇。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容