DEV Community

Omkar Bhagat
Omkar Bhagat

Posted on • Updated on

Tree Problems Made Easy

Tree problems can be a little difficult to wrap your head around, especially when you are a beginner. This post will try and make tree problems easy for you with the help of some examples.

1. Sum of all values

The goal is to get sum of all the values in the tree.

def getSum(root):

  if not root:
    return 0

  return root.val + getSum(root.left) + getSum(root.right)
Enter fullscreen mode Exit fullscreen mode

This is quite simple. The sum of all values in the tree is simply the sum of:

a) the current node
b) the sum of all values on its left
c) the sum of all values on its right

What if there's no node? That should return zero. That's also our base case.

2. Get max value in the tree

The goal here is to get max value from a tree where the values are all >=0.

def getMax(root):

  if not root:
    return 0

  return max(root.val, getMax(root.left), getMax(root.right))
Enter fullscreen mode Exit fullscreen mode

To get the max value in the tree, we return the maximum between:

a) the value of current node
b) the max value on its left
c) the max value on its right

And when there is no node, we return the max as 0. That's also our base case.

3. Get max tree height

The goal here is to find the maximum tree height or the maximum depth of the tree.

For example, the maximum depth of this tree would be 3:

      1
     / \
    2   3
   / \
  4   5
Enter fullscreen mode Exit fullscreen mode
def getHeight(root):

  if not root:
    return 0

  return 1 + max(getHeight(root.left), getHeight(root.right))
Enter fullscreen mode Exit fullscreen mode

To get the max height, we count the current node as 1 and add it to the max height we get from its branches.

If there's no node, we return 0, which is our base case.

4. Check if value exists in a tree

def valExists(root, val):
  if not root:
    return False
  return root.val == val or valExists(root.left, val) or valExists(root.right, val)
Enter fullscreen mode Exit fullscreen mode

The idea is to check if value exists in the root node, or the left branch or the right branch. If so, return True.

If we end up reaching a non-existent (NULL) node, we can safely return False (i.e. we can safely say the value doesn't exist in this Tree).

5. Invert a binary tree

Original:

     1
    / \
   3   2
      / \
     5   4

Inverted:

     1
    / \
   2   3
  / \
 4   5
Enter fullscreen mode Exit fullscreen mode
def invert(root):
  if not root:
    return

  root.left, root.right = root.right, root.left

  invert(root.left)
  invert(root.right)
Enter fullscreen mode Exit fullscreen mode

We can invert a binary tree as follows:

  1. For the current node, swap values of its left and right branches.
  2. Repeat this for the node on the left and the node on the right.
  3. Stop when we hit an empty node.

6. Check if same tree

def sameTree(t1, t2):

  if not t1 and not t2:
    return True

  if t1 and t2:
    return t1.val == t2.val and sameTree(t1.left, t2.left) and sameTree(t1.right, t2.right)

  return False
Enter fullscreen mode Exit fullscreen mode

For both trees to be the same:

  1. both nodes need to exist.
  2. their root values need to match.
  3. their left branch and right branch needs to match (we can check this recursively).

If while checking we end up reaching a position where both branches are missing, we know we have hit the leaf nodes without any mismatch in values between two trees. Therefore we must return True when we hit leaf nodes.

In all other cases, we should return False.

7. Check if subtree of another tree

def isSubTree(root, subRoot):

  if not root:
    return False

  return sameTree(root, subRoot) \
    isSubTree(root.left, subRoot) \
    isSubTree(root.right, subRoot)
Enter fullscreen mode Exit fullscreen mode

To check if one tree is a subtree of another tree, simply re-use the sameTree function from above with the following logic:

  1. Is current node and subRoot the same tree? If so, return True.
  2. Otherwise recursively check its left and right branches.
  3. If we hit a leaf node, we should return False because we haven't found a True value for sameTree function so far.

8. Binary Search Tree (BST): Lowest Common Ancestor

In a BST, all the values on the left are strictly less than the current node while all the values on the right are strictly greater than the current node.

We can use this to our advantage in finding the lowest common ancestor as follows:

def LCAofBST(root, p, q):

  if not root:
    return False

  if p.val < root.val and q.val < root.val:
    return LCAofBST(root.left, p, q)

  if p.val > root.val and q.val > root.val:
    return LCAofBST(root.right, p, q)

  return root
Enter fullscreen mode Exit fullscreen mode

The logic is quite simple:

  1. If there's no root, return False.
  2. If both values are less than root, check the left side.
  3. If both values are greater than root, check the right side.
  4. In all other cases, we know p falls on the left and q falls on the right, this makes the current node the least common ancestor between the two, therefore we return root.

9. Lowest Common Ancestor in a Binary Tree

What if our tree is not a BST? Then finding the LCA is a little difficult but not impossible. Here's how to do it:

def lca(root, p, q):

  if not root:
    return None

  if root.val == p or root.val == q:
    return root

  left_result = lca(root.left, p, q)
  right_result = lca(root.right, p, q)

  if left_result and right_result:
    return root

  if left_result:
    return left_result
  else:
    return right_result
Enter fullscreen mode Exit fullscreen mode

The logic here can be made simple with two key points:

  1. If the left branch and the right branch has p and q respectively, return the current node as LCA.
  2. If only one branch as p or q, return p or q respectively.

For example:

         1
       /   \
      2     3
     / \ 
    4   5 
Enter fullscreen mode Exit fullscreen mode

(A) Finding 4 and 5

  • You hit 4, you return 4.
  • You hit 5, you return 5.
  • You hit 2, you get 4 and 5, so you return 2.
  • You hit 3, you return None.
  • You hit 1, you get 2 and None, you return 2.

(B) Finding 2 and 5

  • You hit 2, you return 2.
  • You hit 3, you return None.
  • You hit 1, you get 2 and None, you return 2.

(C) Finding 2 and 3

  • You hit 2, you return 2.
  • You hit 3, you return 3.
  • You hit 1, you get 2 and 3, you return 1.

10. Final tip

My final tip would be to visualize the tree and try to solve it for smaller problems as follows:

      NULL

       1

       1
      / \
     2   3

       1
      /  
     2     

       1
        \
         3
Enter fullscreen mode Exit fullscreen mode

Imagine having a Null node, a single node, a node with two branches, a node with left branch, and finally a node with right branch.

How would you solve for these cases? This would help you make some progress on the given problem :)

Top comments (0)