Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions 0235.Lowest-Common-Ancestor-of-a-Binary-Search-Tree/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 235. Lowest Common Ancestor of a Binary Search Tree

## step1

root: 'TreeNode'

16mぐらい。BFSで探索し、親ノードを残しておく。

(元のコードにあった型をクォーテーションで囲むのは前方参照を解決するための、`from __future__ import annotation`以前の方法らしい。TreeNodeのメソッドとして書くことを想定したのだろうか?)

binary **search** treeであったことに解き終わってから気がついた。valを探索に使うことができる。そうなると「二つのvalの間にある、深さ最小のノード」が答えとなる。

注:この次の問題がBinary Treeを扱う

https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/?envType=problem-list-v2&envId=rab78cw1

## 他の人のコードなど

> Unreachable なところに raise を書くことですが、ありかもしれませんが、私はそこまで肯定的ではないです。結構微妙なところだと思います。

> まず、一般的に、dead code は避けるものです。
> また、Python の場合、返り値があって到達する場合はあってもなくても同じだが return None を書き、到達しない場合は書かないことで、unreachable かの意図は表現されるはずです。

書かない方が望ましいようだ。

https://github.com/naoto-iwase/leetcode/pull/66

- 3つ以上に一般化するなあどかなり工夫されている
- lower, upper = sorted([p.val, q.val]) の書き方
- p, q自体を交換するよりわかりやすいかも。
- pathを保存しているが、自分のparentのみ保存でも良きがする



- コメント

```python
if lower <= node.val <= upper:
return node
```
を一番初めに持ってきたほうが明確ではないですかね。

- p または q が見つかった場合にはそれを返し、見つからなかった場合には None を返すようなコードで再帰的に処理するイメージです。

「root以下にあるp, qのLCAを返す」アルゴリズム。ボトムアップ再帰。

```python
class Solution:
def lowestCommonAncestor(
self,
root: TreeNode,
p: TreeNode,
q: TreeNode
) -> Optional[TreeNode]:
if root == p or root == q:
return root

left = None
if root.left:
left = self.lowestCommonAncestor(root.left, p, q)

right = None
if root.right:
right = self.lowestCommonAncestor(root.right, p, q)

# 左右両方で見つかれば root が LCA
if left and right:
return root

# 片方だけ見つかった場合、見つかった方を返す
return left if left else right
```

https://github.com/ryosuketc/leetcode_grind75/pull/10

https://github.com/huyfififi/coding-challenges/pull/10

step1_1.pyは上のボトムアップ再帰になっている。

> よく考えたら、せっかくBinary Search Treeが与えられているのに、その性質を利用していなかった。

問題文を読んだ時の誤解が自分だけではなかったのは少し安心

## C++
今回は難しいコードではないので動くものは書けた。
24 changes: 24 additions & 0 deletions 0235.Lowest-Common-Ancestor-of-a-Binary-Search-Tree/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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:
if p.val > q.val:
p, q = q, p # p.val <= q.val
node = root
while True:
if node.val > q.val:
node = node.left
elif node.val < p.val:
node = node.right
else:
return node

raise RuntimeError("unreachable")
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]
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
31 changes: 31 additions & 0 deletions 0235.Lowest-Common-Ancestor-of-a-Binary-Search-Tree/step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Definition for a binary tree node.
#include <algorithm>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q) {
int smaller = std::min(p->val, q->val);
int larger = std::max(p->val, q->val);
TreeNode *node = root;
while (node) {
if (smaller <= node->val && node->val <= larger) {
return node;
}
if (node->val < smaller) {
node = node->right;
continue;
}
if (node->val > larger) {
node = node->left;
continue;
}
}
return nullptr;
}
};
23 changes: 23 additions & 0 deletions 0235.Lowest-Common-Ancestor-of-a-Binary-Search-Tree/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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:
smaller, larger = sorted((p.val, q.val))
node = root
while True:
if smaller <= node.val <= larger:
return node
if node.val < smaller:
node = node.right
continue
if node.val > larger:
node = node.left
return None