【LeetCode算法修炼指南】—— 96.不同的二叉搜索树

末鹿安然 提交于 2020-01-26 23:34:14

1. 题目

原题链接
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

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

1.1. 要点

  1. 掌握二叉搜索树
  2. 假定每一个数字为根结点,并将剩余的数组分配到左右两边继续构造子树。
    此时每一个根结点构成二叉树的数量总和为左边*右边

1.2. 递归

这个版本的实现也是勉勉强强的通过的LeetCode,但是性能非常差。
但是毕竟用了二叉树递归特性来实现,所以还是体会一下吧。

1.2.1. 代码片段

func numTrees(n int) int {
	if n == 0 {
		return 0
	}
	var numTreesMap = make(map[string]int, 0) //如果不用一个hash map来记录已经运算过的结果,直接就导致运算超时。
	return numTreesHelper(1, n, numTreesMap)//注意边界位置[1...n]
}

func numTreesHelper(left, right int, numTreesMap map[string]int) int {
	if left >= right { //由于边界的设定是允许[1...1]这个区间直接返回1种情况。强调一下在编写整个算法过程中,如何保证 循环不变量(loop invariant) 是一个非常重要的过程
		return 1
	}
	var sum int
	for i := left; i <= right; i++ {
		lkey := fmt.Sprintf("%d_%d", left, i-1)
		rkey := fmt.Sprintf("%d_%d", i+1, right)
		var l,r int
		if v, ok := numTreesMap[lkey]; ok { //运算过的直接返回不需要再递归了
			l = v
		}else{
			l = numTreesHelper(left, i-1, numTreesMap)
			numTreesMap[lkey] = l
		}
		if v, ok := numTreesMap[rkey]; ok {
			r = v
		}else{
			r = numTreesHelper(i+1, right, numTreesMap)
			numTreesMap[rkey] = r
		}
		sum += l * r
	}
	return sum
}

1.2.2. 代码解释

  1. 整个循环注意保持 循环不变量(loop invariant) ,这个在循环、递归等过程中是保证结果正确的重要性质。
  2. 如果我们只关注内部的递归过程,不难发现其本质上就是在构建一个二叉搜索树【LeetCode算法修炼指南】——108.将有序数组转换为二叉搜索树 ,
  3. 递归回溯的过程中,记录了当前数组左子树的可能数l以及右子树的可能数rsun += l*r,l与r的结果总数是一个组合的可能性。如果觉得不太好理解那你可以假定现在左子树已经确定了,右边有n中可能,此时可能性是1*n,如果左子树有m种呢?这个乘法的过程称为组合(即笛卡尔积)

1.3. 动态规划

动态规划解法参考官方题解,官方的解释非常清楚,需要一点数学的功底,但总体难说不难理解。

1.3.1. 数学公式推导

假设现在的根节点是3
F(3,7)=G(2)G(4)(1)F(3,7) =G(2)⋅G(4) \qquad(1)
F(i,n)=G(i1)G(ni)(2)F(i,n)=G(i−1)⋅G(n−i) \qquad (2)
G(n)=n=1nG(i1)G(ni)(3)G(n)= \sum^n_{n=1}G(i−1)⋅G(n−i)\qquad (3)

1.3.2. 代码片段

func numTrees3(n int) int {
    G := make([]int, n+1) //[1...n]这个区间,再算上一个0,所以空间总数n+1
    G[0] = 1
    G[1] = 1
    for i := 2; i <= n; i++ {
        for j := 1; j <= i; j++ {
            G[i] += G[j-1] * G[i-j]
        }
    }
    return G[n]
}

1.4. 数学推导“卡塔兰数”

1.4.1. 公式:

C0=1Cn+1=2(2n+1) n+2Cn C_0 = 1 \qquad\qquad C_{n+1} = \frac{2(2n+1)}{\ n+2} C_n

1.4.2. 代码片段

func numTrees(n int) int {
	var num = 1
	for i := 1; i < n; i++ {
		num = num * 2 * (2*i + 1) / (i + 2)
	}
	return num
}

😁😁😁制作动画过程不易,请大家顺手点赞收藏咯 谢谢~😁😁😁
如有错误欢迎指出~
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!