BST Question Summary

Note:
During practice, also practise how to think, how to take proper note to assist thinking, and how to draw the right helper diagram. For example, how to draw:

  • queue
  • stack

How to draw each iteration of while/for loop. How to draw recursion / divide-and-conquer's thinking process.


Questions with easy difficulty:

110. Check Balanced Binary Tree

  • This problem is generally believed to have two solutions: the top down approach and the bottom up way.

1.The first method checks whether the tree is balanced strictly according to the definition of balanced binary tree: the difference between the heights of the two sub trees are not bigger than 1, and both the left sub tree and right sub tree are also balanced. With the helper function depth(), we could easily write the code;

class solution {
  public: 
  int depth (TreeNode *root) { 
    if (root == NULL) return 0; 
    return max (depth(root -> left), depth (root -> right)) + 1; 
  } 
  bool isBalanced (TreeNode *root) { 
    if (root == NULL) return true; 
    int left=depth(root->left); 
    int right=depth(root->right); 
    return abs(left - right) <= 1 && isBalanced(root->left) && isBalanced(root->right); 
  }
};
# use bottom up approach: 
class Solution(object):
    def isBalanced(self, root):
        return self._isBalanced(root) != -1
        
    # think: can we do it without helper function? 
    # how should we modify the return result in that case? 
    def _isBalanced(self, root):
        if not root:
            return 0
        left_height = self._isBalanced(root.left)
        if left_height == -1:
            return -1
        right_height = self._isBalanced(root.right)
        if right_height == -1:
            return -1
        if abs(left_height - right_height) > 1:
            return -1
        return max(left_height, right_height) + 1
        

102. Binary Tree Level Order Traversal

Three compact python solutions

def levelOrder(self, root): 
    ans, level = [], [root] 
    while root and level: 
        ans.append([node.val for node in level]) 
        LRpair = [(node.left, node.right) for node in level] 
        level = [leaf for LR in LRpair for leaf in LR if leaf] 
    return ans

This is equivalent to:

def levelOrder(self, root): 
    if not root: 
        return [] 
    ans, level = [], [root] 
    while level: 
        ans.append([node.val for node in level]) 
        temp = [] 
        # this additional step is to make sure we only add valid (not None) leaf to level. 
        # In graph BST, we assume there won't be such None connection, therefore no need this examination
        for node in level: 
            temp.extend([node.left, node.right]) 
        level = [leaf for leaf in temp if leaf] 
    return ans

This cpp solution is more elegant.

vector> ret;
void buildVector(TreeNode *root, int depth) { 
    if(root == NULL) return; 
    if(ret.size() == depth) 
      ret.push_back(vector());
    ret[depth].push_back(root->val); 
    buildVector(root->left, depth + 1); 
    buildVector(root->right, depth + 1);
  }
  vector > levelOrder(TreeNode *root) { 
    buildVector(root, 0); 
    return ret;
  }

There is other solution which use a queue. Also check them !!

98. Validate Binary Search Tree

def isValidBST(self, root, lessThan = float('inf'), largerThan = float('-inf')): 
    if not root: return True # this is necessary for each isValidBST call
      # it makes sure that the following root.val valid
"""
for tree related questions:
should we always include this 'if not root' check in recursion's base case? 
how is this related with return result? if return is connected by or / and,
how is the result gonna to be different? 
"""
    if root.val <= largerThan or root.val >= lessThan: 
        return False 
    return self.isValidBST(root.left, min(lessThan, root.val), largerThan) and \\
self.isValidBST(root.right, lessThan, max(root.val, largerThan))

Python version based on inorder traversal

class Solution(object):
    def isValidBST(self, root):  
        self.res = list() 
        self.validation(root) 
        return self.res == sorted(self.res) and len(set(self.res)) == len(self.res)

    def validation(self, root): 
        if not root: 
            return 
        else: 
            self.validation(root.left) 
            self.res.append(root.val) 
            self.validation(root.right)

112. Path Sum

def hasPathSum(self, root, sum): 
    if not root: return False # this checking is needed at each call
        # return False also align with final OR return statement 
    if not root.left and not root.right and root.val == sum: 
        return True 
    sum -= root.val 
    return self.hasPathSum(root.left, sum) or self.hasPathSum(root.right, sum)

257. Binary Tree Paths

learn nested list comprehension in this example:

def binaryTreePaths(self, root): 
    if not root: 
        return [] 
    return [str(root.val) + '->' + path for kid in (root.left, root.right) if kid for path in self.binaryTreePaths(kid)] or [str(root.val)]
# NOTE: understand how nested list comprehension works !! 

Another solution (recursion):

def binaryTreePaths(self, root): 
    if not root: return [] 
    if not root.left and not root.right: 
        return [str(root.val)] 
    treepaths = [str(root.val)+'->'+path for path in self.binaryTreePaths(root.left)] 
    treepaths += [str(root.val)+'->'+path for path in self.binaryTreePaths(root.right)] 
    return treepaths

Python solutions (dfs+stack, bfs+queue, dfs recursively).

  • It is extremely important to actually draw the stack and see what's going.
  • It is also important to write out while loop and make good book keeping, when we think about such process.
  • This is a good example shows how to modify the basic DFS/BFS to complete more complicated task
  • It is particular important to notice that how we store extra information in stack/queue to complete our goal. It requires careful design to store the right information
  • In this case, the right complimentary information stored in stack for each node is the complete path from root to this node
  • The intuitive way that BFS/DFS can be tailored to complete this task is because, essentially, this task is about tree traversal. Therefore, we must be able to tailored tree traversal algorithm to handle this task.
# dfs + stack
def binaryTreePaths1(self, root):
    if not root:
        return []
    res, stack = [], [(root, "")] # basic setup
    while stack:
        node, ls = stack.pop() # for each call, initially, it will pop: node=root, ls=""
        if not node.left and not node.right:
            # means we reach leaf, and complete the path
            # append the path into res
            res.append(ls+str(node.val))
        if node.right:
            # path is not completed yet, continue to traversal deeper
            stack.append((node.right, ls+str(node.val)+"->"))
            # notice that how we store additional information in stack
            # the order of left/right doesn't matter. 
            # BFS/DFS don't matter neither. 
        if node.left:
            stack.append((node.left, ls+str(node.val)+"->"))
    return res
# bfs + queue
def binaryTreePaths2(self, root):
    if not root:
        return []
    res, queue = [], [(root, "")]
    while queue:
        node, ls = queue.pop(0)
        if not node.left and not node.right:
            res.append(ls+str(node.val))
        if node.left:
            queue.append((node.left, ls+str(node.val)+"->"))
        if node.right:
            queue.append((node.right, ls+str(node.val)+"->"))
    return res

129. Sum Root to Leaf Numbers

Python solutions (dfs+stack, bfs+queue, dfs recursively)

# Sol1: dfs + stackdef 
sumNumbers1(self, root): 
    if not root: return 0 
    stack, res = [(root, root.val)], 0 
    while stack: 
        node, value = stack.pop() 
        if node: 
            if not node.left and not node.right: 
                res += value 
            if node.right: 
                stack.append((node.right, value*10+node.right.val)) 
            if node.left: 
                stack.append((node.left, value*10+node.left.val)) 
    return res

# Sol2: bfs + queuedef
sumNumbers2(self, root): 
    if not root: return 0 
    queue, res = collections.deque([(root, root.val)]), 0 
    while queue: 
        node, value = queue.popleft() 
        if node: 
            if not node.left and not node.right: 
                res += value 
            if node.left: 
                queue.append((node.left, value*10+node.left.val)) 
            if node.right: 
                queue.append((node.right, value*10+node.right.val)) 
    return res

# Sol3: recursively 
def sumNumbers(self, root): 
    self.res = 0 
    self.dfs(root, 0) 
    return self.res

def dfs(self, root, value): 
    if root: 
        #if not root.left and not root.right: 
        # self.res += value*10 + root.val 
        self.dfs(root.left, value*10+root.val) 
        #if not root.left and not root.right: 
        # self.res += value*10 + root.val 
        self.dfs(root.right, value*10+root.val) 
        if not root.left and not root.right: 
            self.res += value*10 + root.val

100. Same Tree

def isSameTree1(self, p, q): 
    if p and q: 
        return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) 
    else: 
        return p == q
# DFS with stack 
def isSameTree2(self, p, q): 
    stack = [(p, q)] 
    while stack: 
        node1, node2 = stack.pop() 
        if not node1 and not node2: 
            continue 
        elif None in [node1, node2]: 
            return False 
        else: 
            if node1.val != node2.val: 
                return False 
            stack.append((node1.right, node2.right)) 
            stack.append((node1.left, node2.left)) 
    return True
# BFS with queue 
def isSameTree3(self, p, q): 
    queue = [(p, q)] 
    while queue: 
        node1, node2 = queue.pop(0) 
        if not node1 and not node2: 
            continue 
        elif None in [node1, node2]: 
            return False 
        else: 
            if node1.val != node2.val: 
                return False 
            queue.append((node1.left, node2.left)) 
            queue.append((node1.right, node2.right)) 
    return True

Questions with Medium Difficulty:

114. Flatten Binary Tree to Linked List

class Solution: 
    # @param {TreeNode} root 
    # @return {void} Do not return anything, modify root in-place instead. 

    def flatten(self, root): 
        ''' 1. flatten left subtree 
            2. find left subtree's tail 
            3. set root's left to None, root's right to root'left, tail's right to root.right 
            4. flatten the original right subtree ''' 
        # escape condition 
        if not root: 
            return 
        right = root.right 
        if root.left: 
            # flatten left subtree 
            self.flatten(root.left) 
            # find the tail of left subtree 
            tail = root.left 
            while tail.right: 
                tail = tail.right 
                # left <-- None, right <-- left, tail's right <- right 
            root.left, root.right, tail.right = None, root.left, right 
        # flatten right subtree 
        self.flatten(right)

105. Construct Binary Tree from Preorder and Inorder Traversal

# it is important to write python style code !! 
def buildTree(self, preorder, inorder): 
'''
this is a very elegant solution. The order of left and right is CRUCIAL!! 
based on the idea of DFS, the recursion will go to the deepest left, 
which will pop all left side pre-order list node, 
and therefore the right side can work! 
'''
    if inorder: 
        ind = inorder.index(preorder.pop(0)) 
        root = TreeNode(inorder[ind]) 
        root.left = self.buildTree(preorder, inorder[0:ind]) 
        root.right = self.buildTree(preorder, inorder[ind+1:]) 
        return root

106. Construct Binary Tree from Inorder and Postorder Traversal

def buildTree(self, inorder, postorder): 
    if inorder: 
        ind = inorder.index(postorder.pop()) # this is the difference !! 
        root = TreeNode(inorder[ind]) 
        root.right = self.buildTree(inorder[ind+1:], postorder) # this order should be changed!! 
        root.left = self.buildTree(inorder[:ind], postorder) 
        return root

another solution, claim to use less memory

108. Convert Sorted Array to Binary Search Tree

see discussion about pass list and memory consumption here
Hello, maybe this is not perfect place to ask this question. But I am very curious whether we are allowed use a copy of lists in these recursive functions in the interview (num[:mid] and num[mid+1:] are actually creating extra sublists). It works for this problem well but for problems like Construct Binary Tree From Preorder AndInoderTraversal, when I used similar method which is very straightforwad , it works locally but OJ gives memory limit exceed. Later I solved it by dealing with indexes instead of passing copy of lists(300+ms) and improved it with the help of hash table(70+ms) but it took me long time. I hope to use the first solution if possible since we don't have much time.

def sortedArrayToBST(self, num): 
    if not num: return None 
    mid = len(num) // 2 
    root = TreeNode(num[mid]) 
    root.left = self.sortedArrayToBST(num[:mid]) 
    root.right = self.sortedArrayToBST(num[mid+1:]) 
    return root

你可能感兴趣的:(BST Question Summary)