动态规划-递归-递推Unique Binary Search Trees

探讨了如何计算给定n个节点时,结构上不同的二叉搜索树的数量,使用动态规划方法解决,并介绍了卡特兰数的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Given n, how many structurally unique BST's (binary search trees) that store values 1...n?

For example,
Given n = 3, there are a total of 5 unique BST's.

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3
这道题我自己是没啥思路的,感觉就是一种排列组合的计算,但是又不太会。。然后网上查了一下,说要用卡特兰数。。

我下面的带引号的讲解引用Code Ganker(http://blog.youkuaiyun.com/linhuanmars/article/details/24761459)的了:

“这道题要求可行的二叉查找树的数量,其实二叉查找树可以任意取根,只要满足中序遍历有序的要求就可以。从处理子问题的角度来看,选取一个结点为根,就把结点
切成左右子树,以这个结点为根的可行二叉树数量就是左右子树可行二叉树数量的乘积,所以总的数量是将以所有结点为根的可行结果累加起来。写成表达式如下:

熟悉卡特兰数的朋友可能已经发现了,这正是卡特兰数的一种定义方式,是一个典型的动态规划的定义方式(根据其实条件和递推式求解结果)。所以思路也很明确了,维护量res[i]表示含有i个结点的二叉查找树的数量。根据上述递推式依次求出1到n的的结果即可。

时间上每次求解i个结点的二叉查找树数量的需要一个i步的循环,总体要求n次,所以总时间复杂度是O(1+2+...+n)=O(n^2)。空间上需要一个数组来维护,并且需要前i个的所有信息,所以是O(n)。"

 

 

看到了一个解释的更清楚的分析,转自http://fisherlei.blogspot.com/2013/03/leetcode-unique-binary-search-trees.html:

   这题想了好久才想清楚。其实如果把上例的顺序改一下,就可以看出规律了。 

      1         3     3      2      1
       \       /     /      / \      \
        3     2     1      1   3      2
       /     /       \                 \
      2     1         2                 3

 

   比如,以1为根的树有几个,完全取决于有二个元素的子树有几种。同理,2为根的子树取决于一个元素的子树有几个。以3为根的情况,则与1相同。

    定义Count[i] 为以[0,i]能产生的Unique Binary Tree的数目,

    如果数组为空,毫无疑问,只有一种BST,即空树,
    Count[0] =1

    如果数组仅有一个元素{1},只有一种BST,单个节点
    Count[1] = 1

    如果数组有两个元素{1,2}, 那么有如下两种可能
    1                       2
     \                    /
       2                1
    Count[2] = Count[0] * Count[1]   (1为根的情况)
                  + Count[1] * Count[0]  (2为根的情况。

    再看一遍三个元素的数组,可以发现BST的取值方式如下:
    Count[3] = Count[0]*Count[2]  (1为根的情况)
                  + Count[1]*Count[1]  (2为根的情况)
                  + Count[2]*Count[0]  (3为根的情况)

    所以,由此观察,可以得出Count的递推公式为
    Count[i] = ∑ Count[0...k] * [ k+1....i]     0<=k<i-1
    问题至此划归为一维动态规划。

   [Note]
    这是很有意思的一个题。刚拿到这题的时候,完全不知道从那下手,因为对于BST是否Unique,很难判断。最后引入了一个条件以后,立即就清晰了,即
    当数组为 1,2,3,4,.. i,.. n时,基于以下原则的BST建树具有唯一性:
   以i为根节点的树,其左子树由[1, i-1]构成, 其右子树由[i+1, n]构成。 

” 

同时为了根据递推式来写程序,需要将递推式简化一下。

根据卡特兰数,C0Cn+1,因为leetcode输入的参数是n,所以为了避免混淆,这里递推式写成Ct+1,初始值为C0 = 1。

原始的递推式是: Ct+1 += Ci*Ct-i (0<= i <=t)

现在令变量num=t+1,那么t=num-1

所以原始递推式做变量替换得:Cnum += Ci*Cnum-1-i (0<= i <=num-1) 

而num的取值范围是[1, n]因为C0已知。

根据题意分析,二叉搜索树不同原因在于。一个数列中的所有元素都可以成为祖先。 
于是第一个子问题是每个数都可以作为祖先。我们可以遍历数列。

然后确定一个数为祖先后,剩下n-1个数先分为两拨,而这两拨如何分?左子树节点数从0开始一直到某个临界值时,这样过一遍就是完美了。

于是左子树的子树又可以根据这样的递推关系继续往下面分。

当祖先(根为数值为i时),左子树最大的数为i-1。于是左子树的元素个数可以从0增加到i-1。而右子树的节点个数也可以依次增多。

这里的n=3,不是说的根节点数字为3,而是说的当节点数为3的时候

2.确定状态
dp[i]=j 表示当有i的节点时,BST的种类数目。

3.确定初始状态
dp[0]=1; 没有节点只有一种情况,空树 
dp[1]=dp[0]*dp[0]=1; 只有一个节点也是只有一种情况

4.确定状态转移方程
int i;
 for(int j=0;j<i;j++){
    dp[i]+=dp[j]*dp[i-j-1];
 }
1
2
3
4
模拟一下输入3的情况

i                
i=1    dp[1]=dp[0]∗dp[0]dp[1]=dp[0]∗dp[0]            
i=2    dp[2]=dp[1]∗dp[0]dp[2]=dp[1]∗dp[0]    dp[2]=dp[0]∗dp[1]dp[2]=dp[0]∗dp[1]        
i=3    dp[3]=dp[2]∗dp[0]dp[3]=dp[2]∗dp[0]    dp[3]=dp[1]∗dp[1]dp[3]=dp[1]∗dp[1]    dp[3]=dp[0]∗dp[2]dp[3]=dp[0]∗dp[2]    
1. i=1 表示只有一个节点的情况 
2. i=2 表示两个节点的情况,这个时候就可以利用case1中计算出来的结果dp[1]了。 
3. i=3 表示三个节点的情况,这个时候就可以利用case2中计算出来的结果了。

对应i=3的时候 

--------------------- 
作者:飘过的小熊 
来源:优快云 
原文:https://blog.youkuaiyun.com/yinchaoji_/article/details/70137002 
版权声明:本文为博主原创文章,转载请附上博文链接!

 

递推1

//递推解法,先判断是否为1或者2,n大于2时,开始递推
class Solution41 {
    public int numTrees(int n) {
        //n小于2时
        if(n<=2){
           if(n==2){
               return 2;
           }else{
               return 1;
           }
        }else{
            //n大于2时,开始递推
            int res[]=new int[n+1];
            res[0]=1;
            res[1]=1;
            res[2]=2;
            for(int i=3;i<=n;i++){
                int sum=0;
                for(int j=0;j<i;j++){
                    sum+=res[j]*res[i-1-j];
                }
                res[i]=sum;
            }
           return res[n];
        }
    }
}

简洁版本递推解法:

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

递归解法

第一种超时

class Solution{
    public int numTrees(int n) {
        if(n<=1)
            return 1;
        else{
            int sum=0;
            for(int j=0;j<n;j++) {
                sum += numTrees(j) * numTrees(n - 1 - j);
            }
            return sum;
        }
    }
}

第二种AC

class Solution{
    public int numTrees(int n) {
        if(n<=1)
            return 1;
        int sum=0;
        for(int j=0;j<n;j++) {
            sum += numTrees(j) * numTrees(n - 1 - j);
        }
        return sum;
    }
}

第三种简洁版

class Solution {
    public int numTrees(int n) {
        int  f[]=new int[n+10];
        f[0]=f[1]=1;
        for(int i=2;i<=n;i++)
            for(int j=1;j<=i;j++)
                f[i]+=f[j-1]*f[i-j];
        return f[n];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值