Lowest Common Ancestor of a Binary Search Tree

 

  1. Clarify the problem:

    • The problem requires finding the lowest common ancestor (LCA) of two nodes in a binary search tree (BST).
    • We need to find the node in the BST that is the lowest common ancestor of the given two nodes.
    • The LCA of two nodes in a BST is the lowest node that has both of the given nodes as descendants.
    • We should return the LCA node or None if either of the given nodes does not exist in the BST.
  2. Analyze the problem:

    • Input: The root node of a binary search tree and two nodes in the tree.
    • Output: The lowest common ancestor node or None.
    • Constraints:
      • The binary search tree is not empty.
      • The nodes in the binary search tree are distinct.
      • The values of the given two nodes are guaranteed to be in the binary search tree.
  3. Design an algorithm:

    • We can use the properties of a binary search tree to find the lowest common ancestor efficiently.
    • The algorithm can be implemented as a recursive function called lowestCommonAncestor.
    • In the lowestCommonAncestor function, we can compare the values of the current node with the values of the given two nodes.
    • If the values of the given nodes are both smaller than the value of the current node, we can recursively call the function on the left subtree.
    • If the values of the given nodes are both greater than the value of the current node, we can recursively call the function on the right subtree.
    • If neither of the above conditions holds, it means the current node is the lowest common ancestor.
    • We can return the current node as the result.
  4. Explain your approach:

    • We will implement a function called lowestCommonAncestor that takes the root node of a binary search tree and two nodes as input.
    • The lowestCommonAncestor function will be a recursive function that compares the values of the current node with the values of the given two nodes.
    • Based on the comparison, we will make recursive calls on the left or right subtree to find the lowest common ancestor.
    • If neither of the above conditions holds, we will return the current node as the lowest common ancestor.
  5. Write clean and readable code:

    python
  6. class TreeNode: def __init__(self, x): self.val = x self.left = None self.right = None def lowestCommonAncestor(root, p, q): if root.val < p.val and root.val < q.val: return lowestCommonAncestor(root.right, p, q) elif root.val > p.val and root.val > q.val: return lowestCommonAncestor(root.left, p, q) else: return root
  7. Test your code:

    • To test the code, we can create a binary search tree and call the lowestCommonAncestor function on it with different pairs of nodes.
    • Test case 1:
    python
  8. # Binary search tree # 6 # / \ # 2 8 # / \ \ # 0 4 9 # / \ # 3 5 root = TreeNode(6) root.left = TreeNode(2) root.right = TreeNode(8) root.left.left = TreeNode(0) root.left.right = TreeNode(4) root.left.right.left = TreeNode(3) root.left.right.right = TreeNode(5) root.right.right = TreeNode(9) p = TreeNode(2) q = TreeNode(8) print(lowestCommonAncestor(root, p, q).val) # Output: 6
    • Test case 2:
    python
  9. # Binary search tree # 6 # / \ # 2 8 # / \ \ # 0 4 9 # / \ # 3 5 root = TreeNode(6) root.left = TreeNode(2) root.right = TreeNode(8) root.left.left = TreeNode(0) root.left.right = TreeNode(4) root.left.right.left = TreeNode(3) root.left.right.right = TreeNode(5) root.right.right = TreeNode(9) p = TreeNode(3) q = TreeNode(5) print(lowestCommonAncestor(root, p, q).val) # Output: 4
  10. Optimize if necessary:

    • The provided implementation already follows an optimal approach for finding the lowest common ancestor in a binary search tree.
    • The time complexity of the algorithm is O(log n), where n is the number of nodes in the binary search tree.
    • This is because we perform a binary search on the tree by comparing the values of the nodes and traverse either the left or right subtree at each step, effectively reducing the search space by half.
  11. Handle error cases:

    • The code assumes that the binary search tree is not empty, and the given nodes exist in the tree.
    • If the root node is None, the code will not work correctly.
    • We can handle the error case by adding a check at the beginning of the lowestCommonAncestor function to return None if the root node is None.
  12. Discuss complexity analysis:

    • Time complexity: The algorithm has a time complexity of O(log n), where n is the number of nodes in the binary search tree. This is because we perform a binary search on the tree, effectively reducing the search space by half at each step.
    • Space complexity: The space complexity is O(log n) in the worst case due to the recursion stack. This is the maximum height of the binary search tree. However, in the best case, when the binary search tree is perfectly balanced, the space complexity is O(1), as there will be no recursion involved.
Next Post Previous Post