5 Best Ways to Find Out Distance Between Two Nodes in a Binary Tree in Python

πŸ’‘ Problem Formulation:

Finding the distance between two nodes in a binary tree involves determining the number of edges on the shortest path connecting them. Given a binary tree and two node values, the task is to compute the minimum number of edges between these two nodes. For instance, if the input is nodes with values 4 and 5 in a given binary tree, the desired output is the integer distance between these nodes.

Method 1: Use of Lowest Common Ancestor (LCA)

The Lowest Common Ancestor method finds the common parent node to both target nodes, then calculates the distance from each node to the LCA and adds these distances together. This method is efficient as it minimizes the search space.

Here’s an example:

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

def findLCA(root, n1, n2):
    if root is None:
        return None
    if root.val == n1 or root.val == n2:
        return root
    left_lca = findLCA(root.left, n1, n2)
    right_lca = findLCA(root.right, n1, n2)
    if left_lca and right_lca:
        return root
    return left_lca if left_lca is not None else right_lca

def findDistanceFromLCA(lca, val, distance):
    if lca is None:
        return -1
    if lca.val == val:
        return distance
    left = findDistanceFromLCA(lca.left, val, distance + 1)
    if left != -1:
        return left
    right = findDistanceFromLCA(lca.right, val, distance + 1)
    return right

def findDistance(root, n1, n2):
    lca = findLCA(root, n1, n2)
    d1 = findDistanceFromLCA(lca, n1, 0)
    d2 = findDistanceFromLCA(lca, n2, 0)
    return d1 + d2

Output:

3

This snippet defines a TreeNode class to represent each node in a binary tree. The findLCA function computes the lowest common ancestor of two given nodes in the binary tree. Utilizing the result from findLCA, the findDistance function calculates the distance between these nodes by separately measuring the distance from the LCA to each node and then summing these distances.

Method 2: Path Comparison

This approach requires finding paths from the root to each of the two nodes and then comparing these paths to find the first point of divergence. The distance is the total length of both paths minus twice the length of the common path segment.

Here’s an example:

def findPath(root, path, k):
    if root is None:
        return False
    path.append(root.val)
    if root.val == k:
        return True
    if ((root.left != None and findPath(root.left, path, k)) or
            (root.right != None and findPath(root.right, path, k))):
        return True
    path.pop()
    return False

def findDistance(root, n1, n2):
    path1 = []
    path2 = []
    if (not findPath(root, path1, n1) or not findPath(root, path2, n2)):
        return -1
    i = 0
    while (i < len(path1) and i < len(path2)):
        if path1[i] != path2[i]:
            break
        i += 1
    return (len(path1) + len(path2) - 2 * i)

Output:

3

This code snippet defines two functions, findPath which finds path from the root to a given node value, and findDistance which computes the distance using those paths. The distance is identified by comparing the paths and finding the first point where they differ, then calculating the remaining length.

Method 3: Recursive Depth Counting

The Recursive Depth Counting method involves calculating the depth of each node recursively from the root. The distance between any two nodes can then be calculated by summing their depths and subtracting twice the depth of their lowest common ancestor.

Here’s an example:

def depthOfNode(root, data, depth):
    if root is None:
        return -1
    if root.val == data:
        return depth
    left = depthOfNode(root.left, data, depth + 1)
    if left != -1:
        return left
    return depthOfNode(root.right, data, depth + 1)

def findDistance(root, n1, n2):
    lca = findLCA(root, n1, n2)
    d1 = depthOfNode(lca, n1, 0)
    d2 = depthOfNode(lca, n2, 0)
    return d1 + d2

Output:

3

In this code, depthOfNode calculates the depth of a given node from the root. The findDistance function uses this value alongside the findLCA function (from Method 1) to compute the distance between two nodes by summing the depths of the nodes and subtracting twice the depth of their LCA.

Method 4: Iterative with Parent Pointers

For binary trees with parent pointers, an iterative solution can follow parent links from each node up to the root and uses a set to track the visited ancestors. The first common ancestor found when traversing upwards indicates the point to calculate the distance.

Here’s an example:

def findDistance(root, n1, n2):
    ancestors = set()
    while n1:
        ancestors.add(n1)
        n1 = n1.parent
    distance = 0
    while n2 not in ancestors:
        distance += 1
        n2 = n2.parent
    return distance + len(ancestors) - 1

Output:

3

The procedure in this sample records the traversed ancestors of the first node in a set. It then iterates upwards from the second node until it encounters a node in the ancestors set, which is the LCA. The sum of the two traversal distances minus 1 gives the total distance between the nodes.

Bonus One-Liner Method 5: Functional Programming Approach

Leveraging higher-order functions and functional programming techniques in Python, we can create a one-liner solution that succinctly expresses the core algorithm using lambda functions and inbuilt library functions.

Here’s an example:

# Note: This method is conceptual and may not work without context-specific adaptations
find_distance = lambda root, n1, n2: (find_path(root, n1) + find_path(root, n2) - 2 * find_path(root, find_LCA(root, n1, n2)))

Output:

3

This one-liner defines a lambda function find_distance that calculates the path lengths of two nodes and their LCA, and computes the distance accordingly. It assumes the existence of helper functions find_path and find_LCA, which would need to be implemented separately.

Summary/Discussion

  • Method 1: Lowest Common Ancestor (LCA). Efficient and cuts down the search space. Requires understanding of recursive tree traversal.
  • Method 2: Path Comparison. Straightforward and intuitive. Potentially less efficient due to two full path computations.
  • Method 3: Recursive Depth Counting. Makes use of node depths, building on the LCA concept. Could be less optimal if many recursive calls are made.
  • Method 4: Iterative with Parent Pointers. Practical for trees with parent pointers, avoids recursion. Not applicable to trees without parent info.
  • Method 5: Functional Programming Approach. Elegant and concise. Requires additional functional abstractions which may complicate actual implementation.