-
Notifications
You must be signed in to change notification settings - Fork 0
235. Lowest Common Ancestor of a Binary Search Tree #10
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
Open
ryosuketc
wants to merge
1
commit into
main
Choose a base branch
from
235_lowest_common_ancestor_of_a_binary_search_tree
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
41 changes: 41 additions & 0 deletions
41
235_lowest_common_ancestor_of_a_binary_search_tree/memo.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 235. Lowest Common Ancestor of a Binary Search Tree | ||
|
|
||
| https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/ | ||
|
|
||
| ## Comments | ||
|
|
||
| ### step1 | ||
|
|
||
| * どこかで解いた気はするがぱっと解き方わからなかった。 | ||
| * さっと答え見たが、BST であることを失念していた。p, q が同じ側にあれば探索を続け、別の側にあるとわかればそれが LCA | ||
| * 再帰での実装は簡単。特にコメントもなし。 | ||
| * `p_val` や `q_val` を変数に置くべきかは微妙なところ。別にアロー演算子でアクセスしてもいい気がする。ちょっとだけタイプするのが楽。 | ||
|
|
||
| ### step2 | ||
| * `Solution1` | ||
| * 最初 `//Unreachable` のコメントだけ書いていたが、最後の `return root;` ないと怒られる。コードパスの中で全部 return しましょうということだと思うが、たぶんコンパイラが到達するか判断できないからだろう。 | ||
| * > Line 35: Char 5: error: non-void function does not return a value in all control paths [-Werror,-Wreturn-type] | ||
| * `Solution2` | ||
| * https://cpprefjp.github.io/reference/utility/unreachable.html | ||
| * `std::unreachable` というのがあるらしい。 | ||
| * C++ だとこれを書いておくのがいいのかな | ||
| * https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.magvbqix7jf7 は Python の話。 | ||
| * https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.atvjch3as46b | ||
| * C++ に関する話まとまってた | ||
| * `while (1)` でも回避できる。なるほど…読みにくい気がする? | ||
| * そもそも無限ループのコード書いたことないかも。この練習会だと時々出てくるが。たぶんプロダクションで加工というよりは、分岐をわかりやすくしたり、選択肢のひとつとして書いてみている、という気がする。 | ||
| * https://github.com/seal-azarashi/leetcode/pull/21#discussion_r1777314992 | ||
| * > (別のスレッドからこの変数をどうにかしていじるなどがなければ)到達しないと思うのですが「コンパイラが到達しないことを理解してくれるか」は別問題です。一般に、一重ループの停止性問題は決定不能なので、コンパイラが停止するかしないかを100%の精度で当てることはありません。あと、コードを読んでいる人にも、停止性問題を解かせないで欲しいのでコメントを残すなりしましょうか。私は無限ループへの書き換えを推します。 | ||
| * https://github.com/olsen-blue/Arai60/pull/59#discussion_r2030548887 | ||
| * > まず、原理的に、コンピュータがコードが到達可能か不可能かを判定することは不可能です。これはチューリングマシンの停止性問題は判定できないことからいえます。一方で、Java や Rust などは、コンパイル時に型のチェックを真面目にしていて、到達不能なコードかはある程度判定します。しかし、不完全です。このため、正確に考えると到達不可能な箇所であったとしても、コンパイラには分からない場合が多々あります。こういったときに、そこの行に例外を書くことでコンパイル可能になります。何を言っているかというと、上のような事情ならば、例外を書くのはいいですが、そうでなければ、私はデッドコードは基本的に書かないほうがよいと思っています。(せいぜいコメントでいいでしょう。) | ||
| * 結局 `std::unreachable` はありなのかな?個人的には無限ループより良さそうに思うのだけど…。 | ||
| * あ、これめっちゃ新しい (C++23)。なるほどそれ以前の環境を考えると無限ループなどが選択肢に入ってくるのか。 | ||
| * 例外はなんか重いから C++ ではあまり使わないというのも聞いたことがある。 | ||
| * https://www.reddit.com/r/cpp/comments/ikv9kv/should_i_use_c_exceptions/ | ||
| * この辺見ると別に良さそうな気もするけど | ||
| * `Solution3` | ||
| * 無限ループは `while (1)` と `while (true)` どっちがいいんだろう。個人的にはちゃんと bool を渡す方が好きだけど | ||
|
|
||
| ### step3 | ||
|
|
||
| * `std::unreachable` がかっこいい (無限ループを避けたい気持ち) | ||
27 changes: 27 additions & 0 deletions
27
235_lowest_common_ancestor_of_a_binary_search_tree/step1.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /** | ||
| * Definition for a binary tree node. | ||
| * 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 root_val = root->val; | ||
| int p_val = p->val; | ||
| int q_val = q->val; | ||
| if (p_val > root_val && q_val > root_val) { | ||
| // Both p and q are in the right subtree. | ||
| return lowestCommonAncestor(root->right, p, q); | ||
| } | ||
| if (p_val < root_val && q_val < root_val) { | ||
| // Both p and q are in the left subtree. | ||
| return lowestCommonAncestor(root->left, p, q); | ||
| } | ||
| return root; | ||
| } | ||
| }; |
93 changes: 93 additions & 0 deletions
93
235_lowest_common_ancestor_of_a_binary_search_tree/step2.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| /** | ||
| * Definition for a binary tree node. | ||
| * struct TreeNode { | ||
| * int val; | ||
| * TreeNode *left; | ||
| * TreeNode *right; | ||
| * TreeNode(int x) : val(x), left(NULL), right(NULL) {} | ||
| * }; | ||
| */ | ||
|
|
||
| // return | ||
| # include <stack> | ||
|
|
||
| class Solution1 { | ||
| public: | ||
| TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { | ||
| int p_val = p->val; | ||
| int q_val = q->val; | ||
| std::stack<TreeNode*> nodes; | ||
|
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. nodes には高々 1 個までしか要素が詰まれないため、単に TreeNode* で良いように思いました。
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. これ気づいていませんでした。stack で積んでいく、とぃうよりは単一の node を動かして走査している、と理解すべきでした。 |
||
| nodes.push(root); | ||
| while (!nodes.empty()) { | ||
| TreeNode* node = nodes.top(); | ||
| nodes.pop(); | ||
| int node_val = node->val; | ||
| if (p_val > node_val && q_val > node_val) { | ||
| nodes.push(node->right); | ||
| continue; | ||
| } | ||
| if (p_val < node_val && q_val < node_val) { | ||
| nodes.push(node->left); | ||
| continue; | ||
| } | ||
| return node; | ||
| } | ||
| // Unreachable | ||
| return root; | ||
| } | ||
| }; | ||
|
|
||
|
|
||
| // unreachable | ||
| # include <stack> | ||
|
|
||
| class Solution2 { | ||
| public: | ||
| TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { | ||
| int p_val = p->val; | ||
| int q_val = q->val; | ||
| std::stack<TreeNode*> nodes; | ||
| nodes.push(root); | ||
| while (!nodes.empty()) { | ||
| TreeNode* node = nodes.top(); | ||
| nodes.pop(); | ||
| int node_val = node->val; | ||
| if (p_val > node_val && q_val > node_val) { | ||
| nodes.push(node->right); | ||
| continue; | ||
| } | ||
| if (p_val < node_val && q_val < node_val) { | ||
| nodes.push(node->left); | ||
| continue; | ||
| } | ||
| return node; | ||
| } | ||
| std::unreachable(); | ||
| } | ||
| }; | ||
|
|
||
|
|
||
| // 無限ループ | ||
| class Solution3 { | ||
| public: | ||
| TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { | ||
| int p_val = p->val; | ||
| int q_val = q->val; | ||
| std::stack<TreeNode*> nodes; | ||
| nodes.push(root); | ||
| while (true) { | ||
| TreeNode* node = nodes.top(); | ||
| nodes.pop(); | ||
| int node_val = node->val; | ||
| if (p_val > node_val && q_val > node_val) { | ||
| nodes.push(node->right); | ||
| continue; | ||
| } | ||
| if (p_val < node_val && q_val < node_val) { | ||
| nodes.push(node->left); | ||
| continue; | ||
| } | ||
| return node; | ||
| } | ||
| } | ||
| }; | ||
26 changes: 26 additions & 0 deletions
26
235_lowest_common_ancestor_of_a_binary_search_tree/step3.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # include <stack> | ||
|
|
||
| class Solution { | ||
| public: | ||
| TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { | ||
| int p_val = p->val; | ||
| int q_val = q->val; | ||
| std::stack<TreeNode*> nodes; | ||
| nodes.push(root); | ||
| while (!nodes.empty()) { | ||
| TreeNode* node = nodes.top(); | ||
| nodes.pop(); | ||
| int node_val = node->val; | ||
| if (p_val > node_val && q_val > node_val) { | ||
| nodes.push(node->right); | ||
| continue; | ||
| } | ||
| if (p_val < node_val && q_val < node_val) { | ||
| nodes.push(node->left); | ||
| continue; | ||
| } | ||
| return node; | ||
| } | ||
| std::unreachable(); | ||
| } | ||
| }; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
「例外は重い」というのは、return と比べれば重いです。
ここをみると char++ * 100万回が 4 ms で、3906回の exception で1秒くらいかかっています。200マイクロ秒かかるということですね。
https://stackoverflow.com/questions/13835817/are-exceptions-in-c-really-slow
私の直感よりもだいぶ遅いのですが、Exception オブジェクトを作ってスタックを巻き戻しながら誰がキャッチするかを探すということになるので、かなり遅いことは確かです。
ただ、Google 社内で使わない理由は、それよりも大域脱出なのでコードが追えなくなるほうが大きいと私は思っています。Chrome の C++ ソースコードは数万ファイルありますから、読んでいて例外が投げてあったら行き先探すのが辛いですね。
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.
自分が英語をちゃんと読めている自信がないのですが、スタイルガイドには「スクラッチからやり始めなければならないとしたら、選択は変わってくるかもしれない。」といった文言が書いてあります。参考まで。
https://google.github.io/styleguide/cppguide.html#Exceptions
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.
Exception が (原理的に) なぜ遅いのか、というのを考えたことがありませんでした。なるほど。
なるほど、確かにこれは辛いですね。
しかしこうした点は C++ 以外の言語 (Java や Python など) ではそこまで忌避されていないように思います。Java はわかりませんが Python だと例外を返すのはごく普通のことだと思うのですが、それはどういった差があるのでしょう…?
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.
@nodchip さんが引用くださったスタイルガイドなんかを参照していると、「思いから使うな」というよりは、「まあ多少重いのは重いんだけどそれ自体は許容範囲で、どちらかというと大規模コードベースを前提にした時の不都合が大きくなるから使うな」という気がしました。
そういう論理であれば、小さいプロジェクトであれば普通に使ってよい気もしましたが、初心者なのでそのあたりの感覚はずれているかもしれません。。
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.
Java や Python はガーベージコレクションがあるのでスタックの解析が不要で、言語がそもそも遅いのです。あと、言語仕様上書かざるを得ないです。