LC 0863 [M] All Nodes Distance K in Binary Tree - ALawliet/algorithms GitHub Wiki

Trees have direction (parent / child relationships) and don't contain cycles. They fit with in the category of Directed Acyclic Graphs (or a DAG). So Trees are DAGs with the restriction that a child can only have one parent.

use DFS to convert the tree to an undirected graph, then do a graph (not tree) BFS

class Solution:
    def distanceK(self, root: TreeNode, target: TreeNode, K: int) -> List[int]:
        res = []

        # convert tree (directed acyclic graph) to undirected graph adj list
        # to do undirected we need to know the parent
        adj = defaultdict(list)

        def dfs(node, parent):
            # undirected
            if node and parent: # really just have to check parent because there is always a node
                adj[node].append(parent)
                adj[parent].append(node)

            if node.left:
                dfs(node.left, node)

            if node.right:
                dfs(node.right, node)
        
        dfs(root, None)
        
        # now we have a graph, now we can use simply BFS to calculate K distance from node
        Q = deque([(target, 0)])
        visited = set([target])
        
        while Q:
            node, distance = Q.popleft()
            
            if distance == K:
                res.append(node.val)
            
            # adjacency list traversal
            for neighbor in adj[node]:
                if neighbor not in visited:
                    Q.append((neighbor, distance + 1))
                    visited.add(neighbor)
                
        return res
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def distanceK(self, root, target, K):
        conn = defaultdict(list)
        def connect(node, parent):
            # both parent and child are not empty
            if node and parent:
                # building an undirected graph representation, assign the
                # child value for the parent as the key and vice versa
                conn[node.val].append(parent.val)
                conn[parent.val].append(node.val)
            # in-order traversal
            if node.left: connect(node.left, node)
            if node.right: connect(node.right, node)
                
        # the initial parent node of the root is None
        connect(root, None)
        
        # start the breadth-first search from the target, hence the starting level is 0
        bfs = [target.val]
        seen = set(bfs)
        # all nodes at (k-1)th level must also be K steps away from the target node
        for _ in range(K):
            # expand the list comprehension to strip away the complexity
            level = []
            for q_node_val in bfs:
                for connected_node_val in conn[q_node_val]:
                    if connected_node_val not in seen:
                        level.append(connected_node_val)
            bfs = level
            # add all the values in bfs into seen
            seen |= set(bfs)
            
        return bfs
class Solution2:
    def distanceK(self, root: TreeNode, target: TreeNode, K: int) -> List[int]:
        ans = []

        # Return distance from node to target if exists, else -1
        # Vertex distance: the # of vertices on the path from node to target
        def dfs(node):
            if not node:
                return -1
            elif node is target:
                subtree_add(node, 0)
                return 1
            else:
                L = dfs(node.left)
                R = dfs(node.right)
                if L != -1:
                    if L == K: ans.append(node.val)
                    subtree_add(node.right, L + 1)
                    return L + 1
                elif R != -1:
                    if R == K: ans.append(node.val)
                    subtree_add(node.left, R + 1)
                    return R + 1
                else:
                    return -1

        # Add all nodes 'K - dist' from the node to answer.
        def subtree_add(node, dist):
            if not node:
                return
            elif dist == K:
                ans.append(node.val)
            else:
                subtree_add(node.left, dist + 1)
                subtree_add(node.right, dist + 1)

        dfs(root)
        return ans