Sunday, July 23rd

Listing all possible trees is the subject of today's LeetCode problem.

Given a number n, enumerate all possible full binary trees (FBT) with n nodes.  A full binary tree is a tree where every node has either 0 or 2 nodes

Like many tree problems, we can use recursion to find a solution.  We will also use dynamic programming to store intermediate results and avoid duplicate computations

First we declare a hashmap that, for a given value n, stores all possible full binary trees with n nodes

    unordered_map<int, vector<TreeNode*> > memo;

Then we do a few things at the beginning of the function

    vector<TreeNode*> allPossibleFBT(int n) {
        ...
    }

First, since a full binary tree must have an odd number of nodes, we check if n is even

  if(n%2==0) return {};

Then we check the base case as required by recursion.  If n equals 1, then we return an empty node, since that's the only configuration available

    if(n==1) return {new TreeNode(0)};

Third, since we're using dynamic programming, we test is the solution for n nodes has already been found

    if(memo.find(n)!=memo.end()) return memo[n];
 
Now for the meat, we initialize a vector to store results, as well as two variables lcount and rcount.  These last two variables represent the sizes of the left and right subtrees.  lcount starts off equal to 1, and rcount is equal to n-1-lcount, which is the number of nodes less the root node, less the left tree.  

Using left and right subtree sizes, we loop over all possible configuration of sizes for subtrees.  Since a tree can't have an even number of nodes, lcount is incremented by 2 each loop.

    vector<TreeNode*> res;
    int lcount = 1, rcount;
    while(true) {
        rcount=n-1-lcount;
        if(rcount<=0) break;

        ...

        lcount+=2;
    }
        
Now, using recursion, we solve the subproblems of finding all possible full binary trees given the sizes of the left and right subtree

vector<TreeNode*> leftsubtrees = allPossibleFBT(lcount);
    vector<TreeNode*> rightsubtrees = allPossibleFBT(rcount);

So now we vectors that store all possible configurations of FBTs for subtrees of given sides.  How do we create trees from that?  By iterating over all possible right/left subtree combinations, then creating a new TreeNode root that has each left/right combo as a subtree.  Each new root is added to the result vector.

    for(auto lst : leftsubtrees) {
        for(auto rst : rightsubtrees) {
            TreeNode* root = new TreeNode(0, lst, rst);
            res.push_back(root);
        }
    }

Redundant computation may occur repeatedly when right and left subtree sizes have already been solved.  This is mitigated by storing the solution in the memo hashmap, then returning it

     memo[n]=res;
     return memo[n];

That's it.  Challenging problem, good to know how to start ones like these in the future.

Special shout out to user cityofsky for a helpful video

Comments

Popular posts from this blog

Wednesday, September 20th

Monday, September 11th

Tuesday, September 19th