Tackling Tree Data Structure Problems: Tips and Techniques

Aaishwarya Kulkarni

--

Tree data structures can be difficult to grasp due to their hierarchical complexity and diverse traversal techniques. To tackle tree problems on LeetCode more effectively, let’s focus on understanding a few key concepts for better problem-solving.

Brush Up on Key Terminologies

  1. Root: The top node of the tree.
  2. Node: Each element in the tree which contains a value or data.
  3. Parent: A node that has one or more children.
  4. Children: Nodes of a parent node.
  5. Leaf: A node with no children.
  6. Siblings: Nodes that share the same parent.
  7. Height: The height of a tree is the longest path from the root to a leaf node, measured by the total number of edges.
  8. Depth: The number of edges from the root node to that node is called the Depth of that node.
  9. Level: Each step from top to bottom is referred to as a level. The root node is at level 0, its child nodes are at level 1, grandchildren at level 2, and so forth.
  10. Degree of Node: The degree of a node represents the total number of children in it.
  11. Ancestors: An ancestor of a node in a tree is any node that lies on the path from the root to that node, excluding the node itself.
  12. Descendants: A descendant of a node is any node that can be reached by following the child links from that node, including the node itself.

Know the Types of Trees

  1. Binary Tree: A tree where each node has at most two children.
  2. Binary Search Tree (BST): A binary tree where every node in the left subtree is less than the root, and every node in the right subtree is of a value greater than the root.
  3. Balanced Tree: A tree where the height of the left and right subtrees of every node differs by not more than one.
  4. Complete Binary Tree: A binary tree in which all levels are fully filled except possibly the last, which is filled from left to right.
  5. Full Binary Tree: A binary tree in which every node other than the leaves has two children.
  6. Degenerate Binary Tree: Every internal node of the degenerate binary has just one child.
  7. Strict Binary Tree: A strict binary tree is another term for a full binary tree, emphasising that each node has either exactly two children or none (i.e., no node has only one child).
  8. AVL Tree: A self-balancing binary search tree where the difference in heights between the left and right subtrees cannot be more than one for any node.

Learn the Basics of Tree Traversals

  1. In-Order Traversal
    Visit the left subtree, then the root, and the right subtree. This gives nodes in ascending order for binary search trees.
  2. Pre-Order Traversal
    Visit the root first, then the left subtree, followed by the right subtree. Useful for creating a copy of the tree.
  3. Post-Order Traversal
    Visit the left subtree, then the right subtree, and then the root. Often used for deleting a tree.
  4. Level-Order Traversal
    Process nodes level by level from top to bottom and left to right. This is done using a queue.

def inOrder(root):

"""
Inorder - Left → Root → Right
"""
if root:
inOrder(root.left)
print(root.data)
inOrder(root.right)

def preOrder(root):

"""
Preorder: Root → Left → Right
"""
if root:
print(root.data)
inOrder(root.left)
inOrder(root.right)

def postOrder(root):

"""
Postorder: Left → Right → Root
"""
if root:
inOrder(root.left)
inOrder(root.right)
print(root.data)

def levelOrder(root):

result = []
q = collections.deque()

if root:
q.append(root)

while q:

level = []
qlen = len(q)

for _ in range(qlen):

node = q.popleft()
level.append(node)

if node.left:
q.append(node.left)
if node.right:
q.append(node.right)

result.append(level)

return [[i.data for i in li] for li in result]

Master Recursion and Iteration

Recursion, recursion, recursion!!

I can’t emphasize enough the importance of recursion! Trees are inherently recursive in nature. Each node in a tree is a root of a subtree which mirrors the structure of the entire tree. As recursion aligns naturally with this structure and helps break down complex tree operations into simpler subproblems, it is a powerful tool for tree traversal and manipulation.

Let’s look at a leetcode problem where recursion is the best fit!

Given the roots of two binary trees p and q, write a function to check if they are the same or not.

When checking if two trees are the same, the problem can be broken down into smaller steps. Check if the current nodes (p and q) are identical. Recursively check if the left subtrees of both nodes are the same. Recursively check if the right subtrees of both nodes are the same.

This step-by-step breakdown is straightforward and mirrors the tree’s structure, making recursion an intuitive and elegant solution.

def isSameTree(p, q):

if not p and not q:
return True

if p and q and p.data == p.data:
return isSameTree(p.left, q.left) and isSameTree(p.right, q.right)
else:
return False

Iteration:

Iteration is a powerful technique in tree problems because it allows for handling large or deep trees without the risk of stack overflow, provides explicit control over the traversal process, and can be more memory-efficient.

Let’s look at a leetcode problem where iteration is more suitable!

Given the root of a binary tree, imagine yourself standing on the right side of it, returning the values of the nodes you can see ordered from top to bottom.

The right-side view requires capturing the rightmost node at each level of the tree. Level-order traversal (breadth-first search, BFS) is ideal for this because it processes nodes level by level. You process each node in the queue, adding its children (left first, then right) to the queue. At the end of processing each level, the last node processed is the rightmost node

def rightSideViewR(root):

result = []
q = collections.deque([root])

while q:

right_view_node = None
qlen = len(q)

for _ in range(qlen):

node = q.popleft()
if node:
right_view_node = node
q.append(node.left)
q.append(node.right)

if right_view_node:
result.append(right_view_node.data)

return result

Use recursion when the problem follows the natural recursive structure of a tree, the tree is not too deep, and the problem requires dividing it into smaller subproblems.

Use iteration when the tree is very deep (to avoid stack overflow), when performing breadth-first traversal, or when you need more explicit control over the traversal or early termination of the process.

Don’t forget the edge cases

  1. Empty Tree: Many algorithms assume that there’s at least one node to process. An empty tree should return a valid result. For example if you are calculating the height of a tree, the height of an empty tree should return 0.
  2. Trees with Missing Children: Some nodes in the tree might only have one child (either left or right child is missing). You must ensure that your algorithm doesn’t assume both children always exist. Traversal and recursive functions need to handle None (or null) nodes correctly.
  3. Type of Tree: In tree problems, edge cases include skewed trees (left-heavy or right-heavy), where recursion can cause stack overflow, making iterative solutions preferable. Additionally, balanced vs. unbalanced trees impact algorithm efficiency, as operations like search in a balanced Binary Search Tree (BST) take logarithmic time (O(log n)), while in unbalanced trees, they degrade to linear time (O(n)). Understanding tree types such as full, complete, or perfect is also critical for optimizing traversal, insertion, or heap operations.

Thanks for reading! Happy Coding!

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Responses (1)

Write a response