|
1 | 1 | # [Problem 2096: Step-By-Step Directions From a Binary Tree Node to Another](https://leetcode.com/problems/step-by-step-directions-from-a-binary-tree-node-to-another/description/?envType=daily-question)
|
2 | 2 |
|
3 | 3 | ## Initial thoughts (stream-of-consciousness)
|
| 4 | +- I think we can do something like this: |
| 5 | + - Do a breadth-first search to find the start and end nodes |
| 6 | + - As we traverse the tree, we'll copy it-- but instead of using the `TreeNode` class, we'll define a new class that lets us store both the parent of each node (like [yesterday's daily problem](https://leetcode.com/problems/create-binary-tree-from-descriptions/description/?envType=daily-question)) *and* whether the current node is the left or right child of its parent. |
| 7 | + - Once we find both the start and end nodes, we can stop this process (but in the worst case, we'll need to traverse the full tree, which will take $O(n)$ time, where $n$ is the number of nodes-- i.e., we need to visit each node up to one time) |
| 8 | + - Next we need to find a common ancester of both nodes: |
| 9 | + - Lets start from the starting node. Just keep following parents until we get to the root. Store the value of (parent) node that we pass and also its depth (e.g., number of levels up from the starting node). |
| 10 | + - Next let's start from the ending node. Again, keep following parents until we get to the root (in the worst case). But this time, things work a little differently: |
| 11 | + - each time we visit a node, check if it's on the path from the starting node back to the root. once we hit a common ancestor we can stop looking. |
| 12 | + - we'll need to construct the path from the common ancestor down to the end node in reverse order (we could just prepend instructions as we go). if the current node is the right child of its parent, we prepend an "R" to the directions, and so on, until we get to the common ancestor. |
| 13 | + - Finally, prepend $n$ `U`s to the directions, where $n$ is the number of "levels up" from the starting node to the common ancestor. |
| 14 | + - Now we can just return the directions. |
4 | 15 |
|
5 | 16 | ## Refining the problem, round 2 thoughts
|
| 17 | +- We will need to account for the possibility that the start and end nodes are the same, and/or that one (or both) is the root of the tree (in which case "0" `'U'` instructions are needed) |
| 18 | +- Ok, let's go with this... |
6 | 19 |
|
7 | 20 | ## Attempted solution(s)
|
8 | 21 | ```python
|
9 |
| -class Solution: # paste your code here! |
10 |
| - ... |
| 22 | +class TreeNodeWithParentAndDirection(TreeNode): |
| 23 | + def __init__(self, val=0, left=None, right=None, parent=None, dir=None): |
| 24 | + self.val = val |
| 25 | + self.left = left |
| 26 | + self.right = right |
| 27 | + self.parent = parent |
| 28 | + self.dir = dir |
| 29 | + |
| 30 | +class Solution: |
| 31 | + def getDirections(self, root: Optional[TreeNode], startValue: int, destValue: int) -> str: |
| 32 | + start_node = None |
| 33 | + dest_node = None |
| 34 | + |
| 35 | + # breadth-first search |
| 36 | + queue = [TreeNodeWithParentAndDirection(val=root.val, left=root.left, right=root.right)] |
| 37 | + while (len(queue) > 0) and (start_node is None or dest_node is None): |
| 38 | + next_node = queue.pop(0) |
| 39 | + if next_node.val == startValue: |
| 40 | + start_node = next_node |
| 41 | + |
| 42 | + if next_node.val == destValue: |
| 43 | + end_node = next_node |
| 44 | + |
| 45 | + #enqueue the left and right children |
| 46 | + if next_node.left is not None: |
| 47 | + queue.append(TreeNodeWithParentAndDirection(val=next_node.left.val, left=next_node.left.left, right=next_node.left.right, parent=next_node, dir='L')) |
| 48 | + if next_node.right is not None: |
| 49 | + queue.append(TreeNodeWithParentAndDirection(val=next_node.right.val, left=next_node.right.left, right=next_node.right.right, parent=next_node, dir='R')) |
| 50 | + |
| 51 | + # find a path from the start_node back to the root (store in a hash table-- i think this will be better than a list) |
| 52 | + start_to_root = {} |
| 53 | + node = start_node |
| 54 | + count = 0 |
| 55 | + while node.parent is not None: |
| 56 | + start_to_root[node.val] = count |
| 57 | + node = node.parent |
| 58 | + count += 1 |
| 59 | + |
| 60 | + start_to_root[node.val] = count |
| 61 | + |
| 62 | + # now find a path from end_node back to the closest node in start_to_root |
| 63 | + node = end_node |
| 64 | + directions = '' |
| 65 | + while node.val not in start_to_root: |
| 66 | + directions = node.dir + directions |
| 67 | + node = node.parent |
| 68 | + |
| 69 | + return 'U' * start_to_root[node.val] + directions |
11 | 70 | ```
|
| 71 | +- Given test cases pass |
| 72 | +- Let's make up another (larger) tree: |
| 73 | + - `root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]`, `startValue = 3`, `destValue = 6`: pass |
| 74 | + - `root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]`, `startValue = 21`, `destValue = 14`: pass |
| 75 | +- Ok, I think we've got this; submitting... |
| 76 | + |
| 77 | + |
| 78 | + |
| 79 | +- Solved! I'm a little surprised that (a) the runtime is worse than most other solutions-- I'm pretty sure this solution is $O(n)$, and also that (b) the memory use is *better* than most solutions (since we end up potentially copying the entire tree). But (like with yesterday's puzzle) maybe there's an overhead to creating new instances of a class? |
0 commit comments