一、原题链接
[所有可能的满二叉树](https://leetcode-cn.com/problems/all-possible-full-binary-trees/)
二、题解
由题目描述可知,一个满二叉树的子树一定也是一个满二叉树。假定函数FBT(N)返回包含N个结点的所有可能满二叉树的数量,则容易推导出以下公式:
FBT(N) = 1 + FBT(x) + FBT(N-1-x)。
上述式子中,FBT(x)表示其左子树若含有x个结点时,能组成的所有的满二叉树的数目,FBT(N-1-x)表示其右子树含有N-1-x个结点时,能组成的所有的满二叉树的数量;式子右端+1则是表示以当前根结点组成了一棵树。N-1-x则是因为树中一共包含N个结点,减去根结点和左子树中的x个结点,则右子树中有N-1-x个结点。
同时,考虑几种特殊情况,如下:
1、N=0时,是一棵空树,直接返回空列表;
2、N=1时,构成只有一个结点的树;
3、由定义可知,N为偶数时构不成满二叉树。
三、代码实现
具体到代码实现时,选择递归的方式。为了避免重复的计算,我们采用记忆化的方式,利用一个哈希表来记录N个结点能构成的满二叉树的情况,哈希表中key值是结点数目N,对应的value值是结点数目为N时,所有可能的满二叉树组合成的列表,列表中的每一个元素都是一个满二叉树的根结点。
在函数FBT中,首先检查哈希表中是否存在结点为N的情况。不存在的话,则递归处理。
代码中,res中的元素将是满二叉树的根结点,元素的个数则是N个结点能够成的满二叉树的数量。
考虑代码 for leftNode in range(1, N, 2),这里之所以每次循环给leftNode加2,是因为左子树的数量只能是1、3、5、7、9……
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def allPossibleFBT(self, N: int) -> List[TreeNode]:
hashtable = {0:[], 1:[TreeNode(0)]} # 做记忆化用
def FBT(N):
# 首先查看在哈希表中是否已经存在可能的值
if N in hashtable:
return hashtable[N]
# 最终res中记录的是N个结点能组成的满二叉树的所有情况
res = []
for leftNode in range(1,N,2):
rightNode = N-1-leftNode
# FBT(leftNode)中存在结点数为leftNode时能组成的满二叉树的所有情况
for left in FBT(leftNode):
# FBT(rightNode)中存在结点数为rightNode时能组成的满二叉树的所有情况
for right in FBT(rightNode):
# 建立根结点,组合所有情况,得到结点数为N时能组成的满二叉树的情况,并添加到res
root = TreeNode(0)
root.left = left
root.right = right
res.append(root)
hashtable[N] = res
return res
FBT(N)
return hashtable[N]
四、补充
为了方便理解,补充一下对于同一个结点数目N而言,hashtable[N],FBT(N)中到底保存的是啥?
肯定的是,对于同一个N,二者都是列表,列表中保存的是一个个树,保存的内容是一样的。
N=1时,只能组成一种树,hashtable[1] = FBT(1) = [TreeNode(0)],如图:
N=3时,同样只能组成一个树,记为A,hashtable[3] = FBT(3) = [A],A为:
N=5时,能组成两个树,记为A、B,hashtable[5] = FBT(5) = [A、B],A、B如图:
因此代码中,建立双重循环遍历FBT(leftNode)和FBT(rightNode),其实就是遍历两个列表,在循环体中每次取出来左子树和右子树的一种可能情况,建立根结点,组成一个树。