-
Notifications
You must be signed in to change notification settings - Fork 0
Lowest Common Ancestor Of A Binary Tree #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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の解法はハイブリッドと言える。 | ||
| 再帰を使っていない点では優れている。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. node_to_depth_and_parentは全量で子に対する親を入れることになりますが、BFSではなくDFSでやると祖先を一つのスタックで管理できるので、p,qが見つかった時点でそれぞれのスタックのコピーを作り、そのコピーたちの共通部分を取ることで見つけることができ、この辺のコードは少し簡単になります。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DFSとバックトラックを用いた解法をstep2_hybrid_backtrack.pyに追加しました。ロジック(DFSとBFS以外)は同じですが、こちらの方が確かにシンプルだと思います。 |
||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ここで
next_frontier = []をすると、ちょっとだけ計算量が減りますかね。There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この break では for 文の処理を抜けるので、while 文の内側に戻ってしまうというご指摘ですね。
意図が明確になるように、 while 文の条件式を
while frontier and not (found_p and found_q):とする形で修正しました。