Taro Logo

All Possible Full Binary Trees

Medium
Google logo
Google
2 views
Topics:
TreesRecursionDynamic Programming

Given an integer n, return a list of all possible full binary trees with n nodes. Each node of each tree in the answer must have Node.val == 0. Each element of the answer is the root node of one possible tree. You may return the final list of trees in any order. A full binary tree is a binary tree where each node has exactly 0 or 2 children.

For example:

Example 1:

If the input is n = 7, a possible output would be a list containing the root nodes of these trees (represented in a simplified way):

[[0,0,0,null,null,0,0,null,null,0,0],
 [0,0,0,null,null,0,0,0,0],
 [0,0,0,0,0,0,0],
 [0,0,0,0,0,null,null,null,null,0,0],
 [0,0,0,0,0,null,null,0,0]]

Example 2:

If the input is n = 3, the output would be:

[[0,0,0]]

Explain how to efficiently generate all possible full binary trees for a given n, considering the constraints 1 <= n <= 20.

Solution


Let's analyze the problem of generating all possible full binary trees with n nodes, where each node has a value of 0. A full binary tree is a tree in which every node other than the leaves has two children.

Naive Approach

A brute-force approach would involve recursively generating all possible tree structures. For each node, we explore the possibilities of either having no children (if it's a leaf) or having exactly two children. This approach would involve generating many invalid trees before finding the valid full binary trees.

Optimal Approach

A more efficient approach utilizes recursion with memoization. We can observe that the number of nodes in a full binary tree must be odd. The base case is when n = 1, where the tree is simply a single node. For n > 1, we can iterate through all possible sizes of the left subtree, construct the left and right subtrees recursively, and combine them to form the full tree. Memoization helps avoid redundant computations.

Algorithm:

  1. Base Case: If n = 1, return a list containing a single node with value 0.
  2. Invalid Input: If n is even, return an empty list since a full binary tree must have an odd number of nodes.
  3. Memoization: Use a dictionary to store the results of previously computed values of n.
  4. Recursive Step: Iterate through all possible numbers of nodes for the left subtree (from 1 to n - 1, incrementing by 2). Calculate the number of nodes for the right subtree. Recursively generate all possible left and right subtrees. Combine each left and right subtree pair by creating a new root node with value 0 and attaching the subtrees as its children. Add the resulting tree to the list of full binary trees.
  5. Return: Return the list of full binary trees.

Code (Python):

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def allPossibleFBT(n: int) -> list[TreeNode]:
    memo = {}

    def generate(n):
        if n in memo:
            return memo[n]
        
        if n == 1:
            return [TreeNode(0)]
        
        if n % 2 == 0:
            return []
        
        res = []
        for i in range(1, n, 2):
            left_trees = generate(i)
            right_trees = generate(n - 1 - i)
            
            for left in left_trees:
                for right in right_trees:
                    root = TreeNode(0)
                    root.left = left
                    root.right = right
                    res.append(root)
        
        memo[n] = res
        return res

    return generate(n)

Time and Space Complexity

  • Time Complexity: The time complexity is difficult to express in simple terms due to the recursive nature and overlapping subproblems. However, the memoization significantly reduces the computations. It is approximately O(C(n/2)), where C(n/2) is the (n/2)-th Catalan number. Catalan numbers grow faster than exponentially but slower than factorially.
  • Space Complexity: The space complexity is dominated by the memoization table, which stores the results for different values of n. The space complexity is also approximately O(C(n/2)).

Edge Cases

  • n = 1: Should return a single node with value 0.
  • n is even: Should return an empty list.
  • n <= 0: The problem statement says n >= 1, but we can handle n <= 0 by returning an empty list.

This approach efficiently generates all possible full binary trees by leveraging recursion and memoization to avoid redundant computations.