Compute shortest path between tree nodes
Company: Snowflake
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: Given a binary tree and two node values u and v, return the shortest path between them as an ordered list of node values. This Snowflake onsite question tests lowest-common-ancestor logic, path reconstruction, iterative vs. recursive implementation, complexity analysis, and preprocessing strategies (parent pointers, binary lifting, Euler tour + RMQ) for answering many queries efficiently.
Part 1: Core LCA Path Between Two Values
Constraints
- 0 <= len(nodes) <= 100000
- If nodes is non-empty, node 0 is the root.
- Each child index is either -1 or a valid node index.
- The input structure is a valid binary tree.
- All node values are unique.
- The returned path length is not counted as extra space.
Examples
Input: ([[3, 1, 2], [5, 3, 4], [1, 5, 6], [6, -1, -1], [2, 7, 8], [0, -1, -1], [8, -1, -1], [7, -1, -1], [4, -1, -1]], 5, 4)
Expected Output: [5, 2, 4]
Explanation: Node 5 is an ancestor of node 4, so the path goes downward through 2.
Input: ([[3, 1, 2], [5, 3, 4], [1, 5, 6], [6, -1, -1], [2, 7, 8], [0, -1, -1], [8, -1, -1], [7, -1, -1], [4, -1, -1]], 6, 8)
Expected Output: [6, 5, 3, 1, 8]
Explanation: The LCA is the root value 3.
Hints
- Find the path from the root to u and the path from the root to v. Their longest common prefix ends at the LCA.
- Once you know the LCA position, reverse the suffix of the u path up to the LCA, then append the suffix of the v path below the LCA.
Part 2: Compare Recursive and Iterative Tree Path Implementations
Constraints
- 0 <= len(nodes) <= 2000
- If nodes is non-empty, node 0 is the root.
- Each child index is either -1 or a valid node index.
- The input structure is a valid binary tree.
- All node values are unique.
Examples
Input: ([[3, 1, 2], [5, 3, 4], [1, 5, 6], [6, -1, -1], [2, 7, 8], [0, -1, -1], [8, -1, -1], [7, -1, -1], [4, -1, -1]], 6, 4)
Expected Output: [[6, 5, 2, 4], [6, 5, 2, 4]]
Explanation: Both implementations should produce the same path through LCA value 5.
Input: ([[3, 1, 2], [5, 3, 4], [1, 5, 6], [6, -1, -1], [2, 7, 8], [0, -1, -1], [8, -1, -1], [7, -1, -1], [4, -1, -1]], 3, 7)
Expected Output: [[3, 5, 2, 7], [3, 5, 2, 7]]
Explanation: The root is one endpoint of the path.
Hints
- The recursive version can search for a root-to-target path by appending on entry and popping on backtracking.
- The iterative version can build parent pointers with an explicit stack, then walk from each target up to the root.
Part 3: Answer Many Tree Path Queries with Preprocessing
Constraints
- 0 <= len(nodes) <= 100000
- 0 <= len(queries) <= 100000
- If nodes is non-empty, node 0 is the root.
- Each child index is either -1 or a valid node index.
- The input structure is a valid binary tree.
- All node values are unique.
Examples
Input: ([[3, 1, 2], [5, 3, 4], [1, 5, 6], [6, -1, -1], [2, 7, 8], [0, -1, -1], [8, -1, -1], [7, -1, -1], [4, -1, -1]], [[6, 4], [5, 8], [7, 7], [5, 99]])
Expected Output: [[6, 5, 2, 4], [5, 3, 1, 8], [7], []]
Explanation: Several queries reuse the same preprocessed tree. The missing value query returns an empty path.
Input: ([], [[1, 2], [1, 1]])
Expected Output: [[], []]
Explanation: All queries on an empty tree return empty paths.
Hints
- Precompute each node parent and depth. Then binary lifting can find LCAs in O(log n) time.
- Even with fast LCA, outputting the path still costs time proportional to the number of nodes in that path.
Part 4: Robust Path Queries with Duplicate Values and Deep Trees
Constraints
- 0 <= len(nodes) <= 100000
- Node values may be duplicated.
- If nodes is non-empty, node 0 is the root.
- Each child index is either -1 or a valid node index.
- Only nodes reachable from root index 0 are considered present in the tree.
- The implementation should avoid recursion.
Examples
Input: ([[1, 1, 2], [2, 3, -1], [2, -1, 4], [3, -1, -1], [3, -1, -1]], 3, 4)
Expected Output: [3, 2, 1, 2, 3]
Explanation: Values 2 and 3 are duplicated, but node indices make the endpoints unambiguous.
Input: ([[1, 1, 2], [2, 3, -1], [2, -1, 4], [3, -1, -1], [3, -1, -1]], 2, 2)
Expected Output: [2]
Explanation: When both endpoint indices are the same, return that single node value.
Hints
- Use an explicit stack to traverse from the root and build parent pointers for reachable nodes.
- After parent pointers are available, find the first common ancestor by walking upward from one endpoint.