|
| 1 | +from collections import deque |
| 2 | +from unittest import TestCase, main |
| 3 | + |
| 4 | + |
| 5 | +# Definition of TreeNode: |
| 6 | +class TreeNode: |
| 7 | + def __init__(self, x): |
| 8 | + self.val = x |
| 9 | + self.left = None |
| 10 | + self.right = None |
| 11 | + |
| 12 | + |
| 13 | +class Solution: |
| 14 | + def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: |
| 15 | + return self.solve_bfs(root, p, q) |
| 16 | + |
| 17 | + """ |
| 18 | + Runtime: 50 ms (Beats 81.68%) |
| 19 | + Time Complexity:` |
| 20 | + - bfs를 이용해 ancetor와 nodes를 초기화 하는데 트리의 모든 p와 q를 포함할 때까지 모든 node를 조회하는데, O(n) |
| 21 | + - p로 부터 부모를 따라 올라가는데, 트리의 모든 node가 편향적으로 연결된 경우 최대 O(n), upper bound |
| 22 | + - q로 부터 부모를 따라 올라가는데, 단, 위 p의 추적 path와 겹치지 않는 곳만 올라가므로, 총합이 O(n) |
| 23 | + > O(n) + (O(P) + O(Q)) = O(n) + O(n) ~= O(n) |
| 24 | +
|
| 25 | + Memory: 21.20 MB (Beats 14.40%) |
| 26 | + Space Complexity: O(n) |
| 27 | + - p, q가 트리의 리프노드인 경우 dq의 크기는 O(n), upper bound |
| 28 | + - p_ancestors와 q_anscestor의 크기는 합쳐서 O(n) |
| 29 | + > O(n) + O(n) ~= O(n) |
| 30 | + """ |
| 31 | + def solve_bfs(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: |
| 32 | + dq = deque([root]) |
| 33 | + ancestor = {root.val: root.val} |
| 34 | + nodes = {root.val: root} |
| 35 | + while dq: |
| 36 | + if p.val in ancestor and q.val in ancestor: |
| 37 | + break |
| 38 | + |
| 39 | + curr_node = dq.popleft() |
| 40 | + if curr_node.left: |
| 41 | + ancestor[curr_node.left.val] = curr_node.val |
| 42 | + dq.append(curr_node.left) |
| 43 | + nodes[curr_node.left.val] = curr_node.left |
| 44 | + if curr_node.right: |
| 45 | + ancestor[curr_node.right.val] = curr_node.val |
| 46 | + dq.append(curr_node.right) |
| 47 | + nodes[curr_node.right.val] = curr_node.right |
| 48 | + |
| 49 | + p_val = p.val |
| 50 | + p_ancestors = set() |
| 51 | + p_ancestors.add(root.val) |
| 52 | + while p_val in ancestor and p_val != root.val: |
| 53 | + p_ancestors.add(p_val) |
| 54 | + p_ancestors.add(ancestor[p_val]) |
| 55 | + p_val = ancestor[p_val] |
| 56 | + |
| 57 | + q_val = q.val |
| 58 | + q_ancestors = set() |
| 59 | + q_ancestors.add(q_val) |
| 60 | + while q_val in ancestor and q_val not in p_ancestors: |
| 61 | + q_ancestors.add(q_val) |
| 62 | + q_ancestors.add(ancestor[q_val]) |
| 63 | + q_val = ancestor[q_val] |
| 64 | + |
| 65 | + common_ancestor = p_ancestors & q_ancestors |
| 66 | + if common_ancestor: |
| 67 | + return nodes[common_ancestor.pop()] |
| 68 | + else: |
| 69 | + return root |
| 70 | + |
| 71 | + |
| 72 | +class _LeetCodeTestCases(TestCase): |
| 73 | + |
| 74 | + def test_1(self): |
| 75 | + root = TreeNode(5) |
| 76 | + node1 = TreeNode(3) |
| 77 | + node2 = TreeNode(6) |
| 78 | + node3 = TreeNode(2) |
| 79 | + node4 = TreeNode(4) |
| 80 | + node5 = TreeNode(1) |
| 81 | + |
| 82 | + root.left = node1 |
| 83 | + root.right = node2 |
| 84 | + node1.left = node3 |
| 85 | + node1.right = node4 |
| 86 | + node3.left = node5 |
| 87 | + |
| 88 | + p = node5 |
| 89 | + q = node1 |
| 90 | + output = root |
| 91 | + self.assertEqual(Solution().lowestCommonAncestor(root, p, q), output) |
| 92 | + |
| 93 | + def test_2(self): |
| 94 | + root = TreeNode(2) |
| 95 | + node1 = TreeNode(1) |
| 96 | + |
| 97 | + root.left = node1 |
| 98 | + |
| 99 | + p = TreeNode(2) |
| 100 | + q = TreeNode(1) |
| 101 | + output = root |
| 102 | + self.assertEqual(Solution().lowestCommonAncestor(root, p, q), output) |
| 103 | + |
| 104 | + |
| 105 | +if __name__ == '__main__': |
| 106 | + main() |
0 commit comments