Saturday, August 5th

 Unique Binary Search Trees

Given an integer n, return all unique binary search trees with n nodes, with values ranging from 1 to n

Photo courtesy of Sverrir Mirdsson via Wikipedia.  Distributed under the Creative Commons Attribution-Share Alike 3.0 Unported license.  No changes made.

Many binary search tree (BST) problems involve recursion.  All subtrees of binary search trees are themselves binary search trees.  For this problem we will approach it recursively.  Also, since many subproblems may overlap, we include a hashmap to memoize intermediate results and implement top-down dynamic programming.

We create a recursive function that, given a start and end value, generates all possible BSTs with the values [start,end].  We also create a memo map that stores the result for a pair of start/end nodes

    //global hashmap
    map<pair<int, int>, vector<TreeNode*>> memo;

    // recursive function    
    vector<TreeNode*> allPossibleBST(int start, int end) {
        ...
    }

    // main function calling recursive function
    vector<TreeNode*> generateTrees(int n) {
return allPossibleBST(1, n);
}

First, we initialize a vector of TreeNodes to store the function result

vector<TreeNode*> res;

Then, like with all recursive functions, we begin with the base case.  Namely, if the start and end indices don't line up, or if start is greater than end, then return NULL as we've reached the end

if (start > end) {
res.push_back(nullptr);
return res;
}

Similarly, if the memo already contains a result, return it and save some work

    if (memo.find(make_pair(start, end)) != memo.end()) {
return memo[make_pair(start, end)];
}

Now, the recursive function contains two parameters, start and end.  We want to find all possible binary search trees with nodes from start to end.  Initially the start and end values are 0 and n-1, but this applies to all subtrees.

We iterate from start to end, selecting each value as a root.  This means that once a root is found at i, then the left subtree contains nodes [start,i-1] and the right subtree contains nodes [i+1, end].  Using this information we recursively call the BST function.

    for (int i = start; i <= end; ++i) {
vector<TreeNode*> leftSubTrees = allPossibleBST(start, i - 1);
vector<TreeNode*> rightSubTrees = allPossibleBST(i + 1, end);
        ...
    }

Now with all possible left and right subtrees, we iterate through both and generate all possible combinations of left and right

    for (auto left: leftSubTrees) {
    for (auto right: rightSubTrees) {
    ...
}
}

For each combination, we create a root node, set its left and right child to the root of the left and right subtrees.  Then add the tree to the result vector

    TreeNode* root = new TreeNode(i, left, right);
res.push_back(root);

Finally, add the result to the memo, keying the vector of trees with the start and end node values

return memo[make_pair(start, end)] = res;

Thanks to the Editorial for a clear and concise explanation

Comments

Popular posts from this blog

Wednesday, September 20th

Monday, September 11th

Tuesday, September 19th