15 Binary Tree Interview Questions and Answers
Prepare for your technical interview with this guide on binary trees, featuring common questions and detailed answers to enhance your understanding.
Prepare for your technical interview with this guide on binary trees, featuring common questions and detailed answers to enhance your understanding.
Binary trees are a fundamental data structure in computer science, widely used in various applications such as databases, file systems, and network routing algorithms. Their hierarchical nature allows for efficient searching, insertion, and deletion operations, making them a crucial concept for any software developer to master. Understanding binary trees is essential for tackling more complex data structures and algorithms.
This article provides a curated selection of binary tree interview questions designed to test and enhance your understanding of this critical topic. By working through these questions and their detailed answers, you will be better prepared to demonstrate your proficiency in binary trees during technical interviews.
Pre-order traversal in a binary tree involves visiting the root node first, followed by the left subtree, and then the right subtree. This method is useful for creating a copy of the tree or for prefix notation of the tree nodes.
class TreeNode: def __init__(self, value): self.value = value self.left = None self.right = None def pre_order_traversal(root): if root: print(root.value, end=' ') pre_order_traversal(root.left) pre_order_traversal(root.right) # Example usage: # Constructing a simple binary tree # 1 # / \ # 2 3 # / \ # 4 5 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) pre_order_traversal(root) # Output: 1 2 4 5 3
In-order traversal visits nodes in the order: left subtree, root, right subtree. This method is particularly useful for binary search trees (BST) because it visits nodes in ascending order.
class TreeNode: def __init__(self, value=0, left=None, right=None): self.value = value self.left = left self.right = right def in_order_traversal(root): result = [] def traverse(node): if node: traverse(node.left) result.append(node.value) traverse(node.right) traverse(root) return result # Example usage: # Constructing a simple binary tree # 1 # / \ # 2 3 # / \ # 4 5 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) print(in_order_traversal(root)) # Output: [4, 2, 5, 1, 3]
Post-order traversal visits nodes in the order: left subtree, right subtree, and then the root node. This method is useful for applications such as deleting a tree or evaluating expressions.
class TreeNode: def __init__(self, value=0, left=None, right=None): self.value = value self.left = left self.right = right def post_order_traversal(root): result = [] def traverse(node): if node: traverse(node.left) traverse(node.right) result.append(node.value) traverse(root) return result # Example usage: # Constructing a simple binary tree # 1 # / \ # 2 3 # / \ # 4 5 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) print(post_order_traversal(root)) # Output: [4, 5, 2, 3, 1]
The height of a binary tree is the number of edges on the longest path from the root node to a leaf node. To find the height, use a recursive approach to calculate the height of the left and right subtrees and take the maximum of the two, adding one for the current node.
class TreeNode: def __init__(self, value=0, left=None, right=None): self.value = value self.left = left self.right = right def find_height(root): if root is None: return -1 left_height = find_height(root.left) right_height = find_height(root.right) return max(left_height, right_height) + 1 # Example usage: # Constructing a simple binary tree # 1 # / \ # 2 3 # / \ # 4 5 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) print(find_height(root)) # Output: 2
The Lowest Common Ancestor (LCA) in a Binary Search Tree (BST) is the lowest node that has both given nodes as descendants. In a BST, this can be efficiently determined by leveraging the properties of the tree.
class TreeNode: def __init__(self, x): self.val = x self.left = None self.right = None def lowestCommonAncestor(root, p, q): while root: if p.val < root.val and q.val < root.val: root = root.left elif p.val > root.val and q.val > root.val: root = root.right else: return root
To convert a sorted array into a balanced BST, use a recursive approach. The middle element of the array will be the root, and the left and right halves will form the left and right subtrees, respectively.
class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def sortedArrayToBST(nums): if not nums: return None mid = len(nums) // 2 root = TreeNode(nums[mid]) root.left = sortedArrayToBST(nums[:mid]) root.right = sortedArrayToBST(nums[mid+1:]) return root
Level-order traversal, also known as Breadth-First Search (BFS), traverses a binary tree level by level from left to right. This can be implemented using a queue data structure.
from collections import deque class TreeNode: def __init__(self, value=0, left=None, right=None): self.value = value self.left = left self.right = right def level_order_traversal(root): if not root: return [] result = [] queue = deque([root]) while queue: level_size = len(queue) current_level = [] for _ in range(level_size): node = queue.popleft() current_level.append(node.value) if node.left: queue.append(node.left) if node.right: queue.append(node.right) result.append(current_level) return result # Example usage: # Constructing a simple binary tree # 1 # / \ # 2 3 # / \ \ # 4 5 6 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) root.right.right = TreeNode(6) print(level_order_traversal(root)) # Output: [[1], [2, 3], [4, 5, 6]]
Serialization and deserialization of a binary tree are essential for storing and transmitting tree structures. Serialization converts the tree into a string, making it easier to save or send. Deserialization reconstructs the tree from the string.
class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def serialize(root): def helper(node): if not node: return "None," return str(node.val) + "," + helper(node.left) + helper(node.right) return helper(root) def deserialize(data): def helper(nodes): val = next(nodes) if val == "None": return None node = TreeNode(int(val)) node.left = helper(nodes) node.right = helper(nodes) return node return helper(iter(data.split(','))) # Example usage: # root = TreeNode(1, TreeNode(2), TreeNode(3, TreeNode(4), TreeNode(5))) # serialized = serialize(root) # deserialized = deserialize(serialized)
The diameter of a binary tree is the length of the longest path between any two nodes. To find the diameter, calculate the height of the left and right subtrees for each node and keep track of the maximum diameter found.
class TreeNode: def __init__(self, value=0, left=None, right=None): self.value = value self.left = left self.right = right def diameter_of_binary_tree(root): def height_and_diameter(node): if not node: return 0, 0 left_height, left_diameter = height_and_diameter(node.left) right_height, right_diameter = height_and_diameter(node.right) current_diameter = left_height + right_height max_diameter = max(current_diameter, left_diameter, right_diameter) return max(left_height, right_height) + 1, max_diameter return height_and_diameter(root)[1] # Example usage: # Constructing a binary tree # 1 # / \ # 2 3 # / \ # 4 5 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) print(diameter_of_binary_tree(root)) # Output: 3
To construct a binary tree from given inorder and preorder traversals, use the first element of the preorder traversal to identify the root. Find the root in the inorder traversal to determine the left and right subtrees, and repeat recursively.
class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def buildTree(preorder, inorder): if not preorder or not inorder: return None root_val = preorder.pop(0) root = TreeNode(root_val) inorder_index = inorder.index(root_val) root.left = buildTree(preorder, inorder[:inorder_index]) root.right = buildTree(preorder, inorder[inorder_index + 1:]) return root
To find the maximum path sum in a binary tree, consider paths that can start and end at any node. Use a helper function that returns the maximum path sum including the current node and updates the global maximum path sum if the current path sum is higher.
class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def maxPathSum(root): def helper(node): nonlocal max_sum if not node: return 0 left_max = max(helper(node.left), 0) right_max = max(helper(node.right), 0) current_max = node.val + left_max + right_max max_sum = max(max_sum, current_max) return node.val + max(left_max, right_max) max_sum = float('-inf') helper(root) return max_sum # Example usage: # root = TreeNode(1, TreeNode(2), TreeNode(3)) # print(maxPathSum(root)) # Output: 6
An AVL tree is a self-balancing binary search tree. It maintains balance by ensuring that the height difference (balance factor) between the left and right subtrees of any node is at most one. If the balance factor becomes greater than one or less than negative one, the tree performs rotations to restore balance.
There are four types of rotations used to maintain balance in an AVL tree:
Here is a concise example demonstrating a right rotation:
class Node: def __init__(self, key): self.key = key self.left = None self.right = None self.height = 1 def right_rotate(y): x = y.left T2 = x.right x.right = y y.left = T2 y.height = max(height(y.left), height(y.right)) + 1 x.height = max(height(x.left), height(x.right)) + 1 return x def height(node): if not node: return 0 return node.height
Tree rotations change the structure of a binary tree without affecting the in-order sequence of the elements. They are used in maintaining the balance of AVL trees, which are binary search trees with a balance condition that ensures the heights of the two child subtrees of any node differ by at most one.
There are four types of rotations:
Example of a Right Rotation (RR):
class TreeNode: def __init__(self, key): self.left = None self.right = None self.val = key def right_rotate(y): x = y.left T2 = x.right # Perform rotation x.right = y y.left = T2 return x # Example usage root = TreeNode(30) root.left = TreeNode(20) root.left.left = TreeNode(10) # Perform right rotation new_root = right_rotate(root)
Deleting a node from a binary search tree (BST) involves three main scenarios:
1. Deleting a leaf node (a node with no children).
2. Deleting a node with one child.
3. Deleting a node with two children.
For each scenario, the process is as follows:
Here is a concise example to illustrate the deletion process:
class TreeNode: def __init__(self, key): self.left = None self.right = None self.val = key def deleteNode(root, key): if not root: return root if key < root.val: root.left = deleteNode(root.left, key) elif key > root.val: root.right = deleteNode(root.right, key) else: if not root.left: return root.right elif not root.right: return root.left temp = minValueNode(root.right) root.val = temp.val root.right = deleteNode(root.right, temp.val) return root def minValueNode(node): current = node while current.left: current = current.left return current
Binary trees are fundamental data structures in computer science, and various types of binary trees offer different advantages and disadvantages. Here, we will discuss AVL trees, Red-Black trees, and Splay trees.
AVL Trees:
Advantages:
Disadvantages:
Red-Black Trees:
Advantages:
Disadvantages:
Splay Trees:
Advantages:
Disadvantages: