【Leetcode题目记录894】——所有可能的真二叉树

航班晚点到凌晨起飞,在机场随缘看了下leetcode的每日一题。

感觉题目挺有意思也挺有难度的,给一个数n,找出所有可能含 n 个节点的 真二叉树 ,并以列表形式返回。答案中每棵树的每个节点都必须符合 Node.val == 0 (真二叉树 是一类二叉树,树中每个节点恰好有 0 或 2 个子节点。)

这道题我看了题目,没有思路,直接看了答案,答案有两种解题方式,第一种是通过递归,第二种方法是通过动态规划。我根据答案的思路回顾一下。

因为题目中说真二叉树 是一类二叉树,树中每个节点恰好有 0 或 2 个子节点,那么可以知道,最终二叉树的节点的个数数量一定为单数(比如一个根节点+左右子节点,或者只有一个根节点),因此不论使用递归算法还是动态规划算法,都应该现判断节点数量不为单数的情况,以及节点数量为0和节点数量为1的情况,这三种情况都可以直接返回结果。

当且仅当节点数量为大于1的单数(3、5、7、9....)时,讨论递归或者动态规划才有意义。

动态规划方法:

使用动态规划算法,先创建一个列表dp[],其中dp[n]的值代表当节点数为n时,能够生成的所有二叉树列表。

脑补一下,当n=1时,只存在一个val=0的根节点,无子节点,因此d[1]直接返回[0]

当n=3时,有根节点、一个左子树、一个右子树,但是左子树和右子树下不会继续有子树,所以dp[3]=[0,0,0]

再往下推以下,由于真二叉树下的子树也是真二叉树,因此每个节点都会保持0个子节点或者2个子节点,假设总共的节点数为n,根节点的左子树占用j个节点(j必然小于n),那么根节点右子树总共会占用n-1-j个节点。n=3时,去掉根节点占用1个节点数,左右子树节点树加起来为3-1个,其中左子树用掉1个节点数,右子树还剩下3-1-1个节点树可用,

当n=5时,除了根节点外还有4个节点数可用,那么根节点下整个左子树的节点数可以为1个或者3个

当左子树节点总数为1时,左子树只能输出dp[1]=[0],右子树的总结点数为3,而总结点数为3的右子树只有一种输出的可能,即dp[3]=[0,0,0]

当左子树总数为3时,右子树的节点数为1。和上面相反,左子树输出dp[1]=[0],右子树输出dp[3]=[0,0,0]

要找到节点数为n下所有可能的真二叉树,那么就要先遍历左子树节点数为1(dp[1])、3(dp[3])、...、n-2(dp[n-2])的情况,找到分别对应的右子树节点数为n-2(dp[n-2])、...、3(dp[3])、1(dp[1])的情况,再分别组合起来放入列表中。

因此这一段的代码表示为:

for (int i = 3; i <= n; i += 2) {
            for (int j = 1; j < i; j += 2) {
                for (TreeNode leftSubtree : dp[j]) {
                    for (TreeNode rightSubtrees : dp[i - 1 - j]) {
                        TreeNode root = new TreeNode(0, leftSubtree, rightSubtrees);
                        dp[i].add(root);
                    }
                }
            }
        }

复制

将n=3,首次遍历j=1的情况带入:

for (int i = 3;) {
            for (int j = 1) {
                for (TreeNode leftSubtree : [0]) {
                    for (TreeNode rightSubtrees : [0]) {
                        TreeNode root = new TreeNode(0, new TreeNode[0], new TreeNode[0]);
                        dp[3].add(root);
                    }
                }
            }
        }

复制

由于当n=3时,j最大只能为1,因此以上代码直接返回n=3的结果,即new TreeNode(0, new TreeNode[0], new TreeNode[0]) = [0,0,0]

如果n=5,则左子树的节点数j可以等于1或者3,右子树的节点数可以为3或者1,返回结果会出现多个两个数组。

开始说过,要考虑n为0、1和n为偶数时直接返回结果的情况,补充这一部分后,完整的JAVA代码如下:

/**
 * 定义TreeNode类方法,leecode已提前定义好。
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */正式的解答过程如下
class Solution {
    public List<TreeNode> allPossibleFBT(int n) {
        if (n % 2 ==0) { return new ArrayList<TreeNode>();}
        List<TreeNode>[] dp = new List[n+1]
        for (int i=0;i<=n;i++){
            dp[i] = new ArrayList<TreeNode>();
        }
        dp[1].add(new TreeNode(0))
        for (int i =3; i<=n; i+=2){
            for(int j =1; j<i; j+=2){
                for (TreeNode left:dp[j])
                {
                    for(TreeNode right:dp[i-1-j]){
                        TreeNode root =  new TreeNode(0, left, right);
                        dp[i].add(root);
                    }}}}
        return dp[n]}}

复制

此问题还有递归解法,但是飞机要起飞了。。。感觉递归解法更直观一些,直接放答案代码(递归我看的python的)

class Solution:
    def allPossibleFBT(self, n: int) -> List[Optional[TreeNode]]:
        full_binary_trees = []
        if n % 2 == 0:
            return full_binary_trees
        if n == 1:
            full_binary_trees.append(TreeNode(0))
            return full_binary_trees
        for i in range(1, n, 2):
            left_subtrees = self.allPossibleFBT(i)
            right_subtrees = self.allPossibleFBT(n - 1 - i)
            for left_subtree in left_subtrees:
                for right_subtree in right_subtrees:
                    root = TreeNode(0, left_subtree, right_subtree)
                    full_binary_trees.append(root)
        return full_binary_trees

作者:力扣官方题解
链接:https://leetcode.cn/problems/all-possible-full-binary-trees/solutions/2713780/suo-you-ke-neng-de-zhen-er-cha-shu-by-le-1uku/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值