diff --git a/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/memo.md b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/memo.md new file mode 100644 index 0000000..4c48ac3 --- /dev/null +++ b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/memo.md @@ -0,0 +1,23 @@ +# 236. Lowest Common Ancestor of a Binary Tree + +## step1 +pとqが見つかるまで木を探索し、見つかったら +以前"Lowest Common Ancestor of a Binary Search Tree"で書いた解法。 + +## 他の人のコード +https://github.com/huyfififi/coding-challenges/pull/46 + +かなりシンプルな解法になっている。自分の解法が冗長であったことを認識した。 + +左右に分岐する点を求める解法は計算量的に O(N^2)になってしまう(step2_top_down.py)。 +この解法はtop-downであると言えるだろう。 + +> lowest common ancestorの解釈を少し変えているのでやや邪道な感じがするが、`p`と`q`が左右に分かれているパターンを判定するために、`q`を含まない`p`をrootとした部分木においても、`p`がlowest common ancestorだとしておく(`q`が部分木のrootの場合も同様に)。 + +この広義の定義と狭義の定義を区別した解法が「Cracking The Coding Interviewに載っていた」という解法であることも認識した。 + +この解法はbottom-upといえる(step2_bottom_up.py)。 +時間計算量O(N)、空間計算量O(h)(最悪O(N)) + +top-down、bottom-upという観点でいえば、step1の解法はハイブリッドと言える。 +再帰を使っていない点では優れている。 diff --git a/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step1_hybrid.py b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step1_hybrid.py new file mode 100644 index 0000000..1110873 --- /dev/null +++ b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step1_hybrid.py @@ -0,0 +1,51 @@ +# Definition for a binary tree node. +class TreeNode: + def __init__(self, x): + self.val = x + self.left = None + self.right = None + + +class Solution: + def lowestCommonAncestor( + self, root: TreeNode, p: TreeNode, q: TreeNode + ) -> TreeNode: + node_to_depth_and_parent: dict[TreeNode, tuple[int, TreeNode | None]] = {} + + depth = 1 + frontier = [(root, None)] + found_p = False + found_q = False + while frontier: + next_frontier = [] + for node, parent in frontier: + if node is None: + continue + node_to_depth_and_parent[node] = (depth, parent) + if node == p: + found_p = True + if node == q: + found_q = True + if found_p and found_q: + break + next_frontier.append((node.left, node)) + next_frontier.append((node.right, node)) + depth += 1 + frontier = next_frontier + + depth_p, parent_p = node_to_depth_and_parent[p] + depth_q, parent_q = node_to_depth_and_parent[q] + if depth_p < depth_q: + depth_p, depth_q = depth_q, depth_p + parent_p, parent_q = parent_q, parent_p + p, q = q, p + while depth_p > depth_q: + p = parent_p + depth_p, parent_p = node_to_depth_and_parent[p] + while p != q: + p = parent_p + depth_p, parent_p = node_to_depth_and_parent[p] + q = parent_q + depth_q, parent_q = node_to_depth_and_parent[q] + + return p diff --git a/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step1_hybrid_revised.py b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step1_hybrid_revised.py new file mode 100644 index 0000000..c2b4843 --- /dev/null +++ b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step1_hybrid_revised.py @@ -0,0 +1,51 @@ +# Definition for a binary tree node. +class TreeNode: + def __init__(self, x): + self.val = x + self.left = None + self.right = None + + +class Solution: + def lowestCommonAncestor( + self, root: TreeNode, p: TreeNode, q: TreeNode + ) -> TreeNode: + node_to_depth_and_parent: dict[TreeNode, tuple[int, TreeNode | None]] = {} + + depth = 1 + frontier = [(root, None)] + found_p = False + found_q = False + while frontier and not (found_p and found_q): + next_frontier = [] + for node, parent in frontier: + if node is None: + continue + node_to_depth_and_parent[node] = (depth, parent) + if node == p: + found_p = True + if node == q: + found_q = True + if found_p and found_q: + break + next_frontier.append((node.left, node)) + next_frontier.append((node.right, node)) + depth += 1 + frontier = next_frontier + + depth_p, parent_p = node_to_depth_and_parent[p] + depth_q, parent_q = node_to_depth_and_parent[q] + if depth_p < depth_q: + depth_p, depth_q = depth_q, depth_p + parent_p, parent_q = parent_q, parent_p + p, q = q, p + while depth_p > depth_q: + p = parent_p + depth_p, parent_p = node_to_depth_and_parent[p] + while p != q: + p = parent_p + depth_p, parent_p = node_to_depth_and_parent[p] + q = parent_q + depth_q, parent_q = node_to_depth_and_parent[q] + + return p diff --git a/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_bottom_up.cpp b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_bottom_up.cpp new file mode 100644 index 0000000..1ec5ea8 --- /dev/null +++ b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_bottom_up.cpp @@ -0,0 +1,29 @@ + +// Definition for a binary tree node. +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} +}; + +class Solution { + public: + TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q) { + if (root == nullptr) { + return nullptr; + } + if (root == p || root == q) { + return root; + } + TreeNode *left_found = lowestCommonAncestor(root->left, p, q); + TreeNode *right_found = lowestCommonAncestor(root->right, p, q); + if (left_found == nullptr) { + return right_found; + } + if (right_found == nullptr) { + return left_found; + } + return root; + } +}; diff --git a/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_bottom_up.py b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_bottom_up.py new file mode 100644 index 0000000..aec00c0 --- /dev/null +++ b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_bottom_up.py @@ -0,0 +1,25 @@ +# Definition for a binary tree node. +class TreeNode: + def __init__(self, x): + self.val = x + self.left = None + self.right = None + + +class Solution: + def lowestCommonAncestor( + self, root: TreeNode | None, p: TreeNode, q: TreeNode + ) -> TreeNode | None: + if root is None: + return None + if root == p or root == q: + return root + + left_found = self.lowestCommonAncestor(root.left, p, q) + right_found = self.lowestCommonAncestor(root.right, p, q) + + if left_found is None: + return right_found + if right_found is None: + return left_found + return root diff --git a/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_hybrid_backtrack.py b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_hybrid_backtrack.py new file mode 100644 index 0000000..e9ecf76 --- /dev/null +++ b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_hybrid_backtrack.py @@ -0,0 +1,41 @@ +# Definition for a binary tree node. +class TreeNode: + def __init__(self, x): + self.val = x + self.left = None + self.right = None + + +class Solution: + def lowestCommonAncestor( + self, root: TreeNode, p: TreeNode, q: TreeNode + ) -> TreeNode: + path_to_p = [] + path_to_q = [] + + def dfs(node: TreeNode | None, path: list[TreeNode]): + if node is None or (path_to_p and path_to_q): + return + + path.append(node) + + if node == p: + path_to_p.extend(path) + if node == q: + path_to_q.extend(path) + + dfs(node.left, path) + dfs(node.right, path) + + path.pop() + + dfs(root, []) + + node = root + for node_p, node_q in zip(path_to_p, path_to_q): + if node_p == node_q: + node = node_p + else: + break + + return node diff --git a/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_top_down.py b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_top_down.py new file mode 100644 index 0000000..1ef73a8 --- /dev/null +++ b/0236.Lowest-Common-Ancestor-of-a-Binary-Tree/step2_top_down.py @@ -0,0 +1,35 @@ +# Definition for a binary tree node. +class TreeNode: + def __init__(self, x): + self.val = x + self.left = None + self.right = None + + +class Solution: + def lowestCommonAncestor( + self, root: TreeNode, p: TreeNode, q: TreeNode + ) -> TreeNode: + def contain(node: TreeNode, target: TreeNode) -> bool: + if node is None: + return False + if node == target: + return True + return contain(node.left, target) or contain(node.right, target) + + def lowest_common_ancester_helper( + node: TreeNode, target1: TreeNode, target2: TreeNode + ): + if node == target1 or node == target2: + return node + + left_contain_1 = contain(node.left, target1) + left_contain_2 = contain(node.left, target2) + + if left_contain_1 and left_contain_2: + return lowest_common_ancester_helper(node.left, target1, target2) + if not (left_contain_1 or left_contain_2): + return lowest_common_ancester_helper(node.right, target1, target2) + return node + + return lowest_common_ancester_helper(root, p, q)