Taro Logo

All Possible Full Binary Trees

Medium
Amazon logo
Amazon
3 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:

Input: n = 7

Output: [[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:

Input: n = 3

Output: [[0,0,0]]

Constraints:

  • 1 <= n <= 20

Solution


Naive Solution

A brute-force approach would involve generating all possible binary trees with n nodes and then filtering out the ones that are not full binary trees. However, this is highly inefficient as the number of possible binary trees grows exponentially with n.

Optimal Solution

A more efficient approach involves using recursion and dynamic programming. The key idea is that a full binary tree with n nodes can be constructed by combining smaller full binary trees. Specifically, for a full binary tree with n nodes, the root node has two subtrees. If the left subtree has i nodes, then the right subtree must have n - 1 - i nodes (where 1 is subtracted for the root node itself). Both subtrees must also be full binary trees.

We can use memoization to store the results of subproblems and avoid recomputation.

Algorithm:

  1. Define a recursive function allPossibleFBT(n) that returns a list of all possible full binary trees with n nodes.
  2. If n is even, return an empty list because a full binary tree must have an odd number of nodes.
  3. If n is 1, return a list containing a single node with value 0.
  4. If the result for n is already memoized, return it.
  5. Iterate through all possible sizes of the left subtree, i, from 1 to n - 1 with a step of 2 (since the left subtree must also have an odd number of nodes).
  6. For each i, recursively generate all possible left subtrees with i nodes and all possible right subtrees with n - 1 - i nodes.
  7. Combine each left subtree with each right subtree by creating a new root node with value 0 and attaching the left and right subtrees.
  8. Add the resulting tree to the list of full binary trees.
  9. Memoize the result for n and return it.

Code (Python):

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

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

        def helper(n):
            if n in memo:
                return memo[n]
            
            if n % 2 == 0:
                return []
            
            if n == 1:
                return [TreeNode(0)]
            
            res = []
            for i in range(1, n, 2):
                left_trees = helper(i)
                right_trees = helper(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 helper(n)

Edge Cases:

  • If n is even, there are no full binary trees, so return an empty list.
  • If n is 1, there is only one full binary tree, which is a single node.

Big O Analysis:

  • Time Complexity: O(C(n/2)), where C(n) is the nth Catalan number. This is because the number of full binary trees grows according to the Catalan numbers. With memoization, we compute each subproblem only once.
  • Space Complexity: O(C(n/2)) due to memoization. We store the results for each subproblem in the memo dictionary, and the maximum size of the memo dictionary is proportional to the number of full binary trees.