前端刷题路-Day77:不同的二叉搜索树(题号96)

270 阅读1分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

不同的二叉搜索树(题号96)

题目

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

img

输入:n = 3
输出:5

示例 2:

输入:n = 1
输出:1

提示:

  • 1 <= n <= 19

链接

leetcode-cn.com/problems/un…

解释

这题啊,这题是小斐波那契数列。

说实话,原理还是十分类似的,想想斐波那契数列是怎么求解的?

function fib(n) {
  const arr = [0, 1]
  for (let i = 2; i <= n; i++) {
    arr[i] = arr[i - 1] + arr[i - 2]    
  }
  return arr[n]
}

这里的求解方法是缓存之前的值,根据旧的值推导出新的值,这题也是同样的想法。

求1和2就不说了,想想3是什么情况,因为是二叉搜索树,所以从上到下的顺序只有一种,所以只要考虑左右两棵树的情况即可。

如果总共有3个节点,肯定有一个是头节点,这里不表。

那么左右子树有多少种可能呢?

左子树节点数右子树节点数可能性
022
111
202

这么一看是不是就清楚了,如果觉得不清楚咱们再看看4个节点的情况:

左子树节点数右子树节点数可能性
035
122
212
305

对吧,可能性的多少和节点的个数是有一定规律的,规律也很简单,就是用左右子树分配节点的个数,那么在拿到节点个数后,首先需要去掉一个头节点,之后依次分配给左右子树,取它们可能性的乘积即可。

不过这里需要注意一点,在0节点的时候,需要按照可能性为1来处理,要不然0乘以任何数都为0,这不直接GG了么。

那么现在,只需要一个时间复杂度为O(nlogn)的算法就可以了,上代码!

自己的答案(双循环)

function numTrees(n) {
  const arr = [0, 1, 2]
  for (let i = 0; i <= n; i++) {
    let count = 0
    for (let j = 0; j < i; j++) {
      if (!j || j === i - 1) {
        count += Math.max(arr[j], arr[i - 1])
      } else {
        count += arr[j] * arr[i - 1 - j]
      }
    }    
    if (!arr[i]) arr[i] = count 
  }
  return arr[n]
};

笔者的方法比官方复杂了一些,主要是考虑到了n为0的情况,其实完全不需要考虑,因为官方已经给定了条件:

1 <= n <= 19

这是完全不需要担心的事情,只需要把数组种的0改成1就会简单很多。

更好的方法(双循环)

官方答案看起来就清爽很多了:

function numTrees(n) {
  const arr = new Array(n + 1).fill(0)
  arr[0] = 1
  arr[1] = 1
  for (let i = 2; i <= n; i++) {
    for (let j = 1; j <= i; j++) {
      arr[i] += arr[j - 1] * arr[i- j]      
    }
  }
  return arr[n]
}

因为去掉了n为0的情况,而且第一层循环是从2开始的,所以很多特殊情况都不需要考虑了,而且在一开始给定了数组长度,这样就不用像笔者一样需要通过count变量来累计可能性了,直接在数组上进行修改即可,方便快捷。

更好的方法(数学)

这一部分就不解释了,据说是 卡塔兰数 ,笔者也没有研究。

var numTrees = function(n) {
    let C = 1;
    for (let i = 0; i < n; ++i) {
        C = C * 2 * (2 * i + 1) / (i + 2);
    }
    return C;
};

奇思妙想

这题有一个答案看起来很有趣,经典的面向测试用例编程,哈哈哈。

class Solution {
public:
    int numTrees(int n) {
        switch(n){
            case 1: return 1;
            case 2: return 2;
            case 3: return 5;
            case 4: return 14;
            case 5: return 42;
            case 6: return 132;
            case 7: return 429;
            case 8: return 1430;
            case 9: return 4862;
            case 10: return 16796;
            case 11: return 58786;
            case 12: return 208012;
            case 13: return 742900;
            case 14: return 2674440;
            case 15: return 9694845;
            case 16: return 35357670;
            case 17: return 129644790;
            case 18: return 477638700;
            case 19: return 1767263190;
            default: return 0;
        }
    }
};

非常切合题意, 可真是个小机灵鬼儿 。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)