-
Notifications
You must be signed in to change notification settings - Fork 0
98. validate binary seach tree #35
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,246 @@ | ||
| ### Step1 | ||
|
|
||
| - 本当はFalseが来た時にすぐ返したい。(いちいちmin, maxを計算するのも大変)nonlocalの方がよかったか | ||
| - でもnonlocalはnonlocalで実装がちょっと見にくいかな | ||
| - returnする値も多い気もする | ||
| - 引数が多いのと、returnする値が多いのと、どちらの方がいいのか、選択の基準を持てていない | ||
| - calculate_min_max_and_check_validは一つの関数で2つの動作、役割があり、微妙な気もするが、しょうがない? | ||
|
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. この点でもstep2の方が自然なのかもしれないですね。 |
||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| def calculate_min_max_and_check_valid(node): | ||
|
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. クラスかnamedtupleかdataclass辺りを作って返した方が分かりやすいかもしれません。いい名前が思いつかないですが...
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. namedtuple, dataclass共、名前は聞いたことあるけどこういう時使うんだなあと思いました。 |
||
| if not node: | ||
| return inf, -inf, True | ||
|
|
||
| def is_current_node_valid(node): | ||
| return left_max < node.val < right_min | ||
|
Comment on lines
+17
to
+18
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. これも中で定義しない方がいいかもですね。 |
||
|
|
||
| left_min, left_max, left_valid = calculate_min_max_and_check_valid( | ||
| node.left | ||
| ) | ||
| right_min, right_max, right_valid = calculate_min_max_and_check_valid( | ||
| node.right | ||
| ) | ||
| min_so_far = min(left_min, right_min, node.val) | ||
| max_so_far = max(left_max, right_max, node.val) | ||
| valid_so_far = left_valid and right_valid and is_current_node_valid(node) | ||
nittoco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return min_so_far, max_so_far, valid_so_far | ||
|
|
||
| _, _, is_valid = calculate_min_max_and_check_valid(root) | ||
| return is_valid | ||
| ``` | ||
|
|
||
| ## Step2 | ||
|
|
||
| - [探索順自体をin-orderにする方法もあった](https://github.com/TORUS0818/leetcode/pull/30/files) | ||
| - あと、math.infがマジックナンバーぽいという意見もあった。すぐわかるものだと思っていた。 | ||
|
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. 一般論として、便宜上minにinfやmaxに-infを入れることはありますが、min/maxとしては実際の値ではないので、calculate_miin_maxを含む名前の関数で最小値としてinfが返ってくると、ちょっとぎょっとするかもしれないですね。個人的にはこのminやmaxの型はOptional[int]みたいな方が安心するかもしれません。 (あと、pythonだとvalの型が明示されていないですが、他の言語を選択するとvalの型はintとかIntとかなので、infはfloatみたいな型の話もあるかもしれません。)
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. ありがとうございます。 |
||
| - https://github.com/kazukiii/leetcode/pull/29/files | ||
| - Noneも入れちゃう実装が好きだったが、入れない方が早くなるかもという | ||
| - &の不要(?) | ||
| - https://github.com/sakupan102/arai60-practice/pull/29/files | ||
| - float(”inf”)とmath.infがあるの知らなかった(実装を読んでみる) | ||
| - float(”inf”)は[PyFloatFromString](https://github.com/python/cpython/blob/ce4b9c8464706a58d0c98c2b0deeec07e7496ccc/Objects/floatobject.c#L178)から[_Py_string_to_number_with_underscores](https://github.com/python/cpython/blob/main/Python/pystrtod.c#L345)を呼び出される。その後結局、innerfuncである[float_from_string_inner](https://github.com/python/cpython/blob/ce4b9c8464706a58d0c98c2b0deeec07e7496ccc/Objects/floatobject.c#L138) が呼び出されて、[PyOS_string_to_double](https://github.com/python/cpython/blob/main/Python/pystrtod.c#L299)が来て、[_PyOS_ascii_strtod](https://github.com/python/cpython/blob/main/Python/pystrtod.c#L93)が来て、[_Py_parse_inf_or_nan](https://github.com/python/cpython/blob/main/Python/pystrtod.c#L28)がくる(やっと見つかった、、、)ここで頑張って+-符号を含めて文字列をparseしている、なぜかPy_HUGE_VALになっている | ||
| - math.infはどこで定義されているのかよくわからない | ||
| - sys.maxsizeについて | ||
| - intっぽいのでこっちの方がいいかも([参考](https://docs.python.org/ja/3/whatsnew/3.0.html#integers)) 厳密な最大ではないっぽいが | ||
|
|
||
| > • 整数の上限がなくなったため、`sys.maxint` 定数は削除されました。しかしながら、通常のリストや文字列の添え字よりも大きい整数として [`sys.maxsize`](https://docs.python.org/ja/3/library/sys.html#sys.maxsize) を使うことができます。 [`sys.maxsize`](https://docs.python.org/ja/3/library/sys.html#sys.maxsize) は実装の "自然な" 整数の大きさに一致し、同じプラットフォームでは (同じビルドオプションなら) 過去のリリースの `sys.maxint` と普通は同じです。 | ||
| > | ||
| - https://github.com/fhiyo/leetcode/pull/30#discussion_r1660235486 | ||
| - iterativeでやる方法 | ||
| - inorderもiterativeでできる(スタック領域におけるリターンアドレスのようなものを入れればできるの賢い) | ||
| - よく考えれば前やった行きと帰りをstackに入れるやつの応用か | ||
| - https://discord.com/channels/1084280443945353267/1200089668901937312/1213043194242015252 | ||
| - inorderのいろんな書き方 | ||
|
|
||
| 1, preorder, 上からvalidateする方法、再帰、nodeがNoneの時は別で実装してinfを避ける | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| def is_valid_val(node, left_min, right_max): | ||
| if not node: | ||
| return True | ||
| if left_min is not None and left_min >= node.val: | ||
| return False | ||
| if right_max is not None and right_max <= node.val: | ||
| return False | ||
| return is_valid_val(node.left, left_min, node.val) \ | ||
| and is_valid_val(node.right, node.val, right_max) | ||
|
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. 上の行の続きならそれと分かるようにスペースをいくつか入れて行の先頭がずれるようにした方が良い気がします。 |
||
|
|
||
| return is_valid_val(root, None, None) | ||
| ``` | ||
|
|
||
| 2, inorderでの再帰 yield fromの利用 | ||
|
|
||
| - generatorにすればfor文で回せるので、単純なreturnより前との比較が楽 | ||
|
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. Generator + 再帰、強力ですね。pros cons の cons としては、再帰の深さの限界があること、走りかけの Generator を木の深さ分だけ作るのでそこそこ重いことがありそうですね。
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. Consのところいまいちわかってなかったので参考になりました。ありがとうございます。 |
||
|
|
||
| ```python | ||
| class Solution: | ||
| def generate_nodes_inorder(self, node): | ||
| if not node: | ||
| return | ||
| yield from self.generate_nodes_inorder(node.left) | ||
| yield node | ||
| yield from self.generate_nodes_inorder(node.right) | ||
|
|
||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| prev_val = None | ||
| for node in self.generate_nodes_inorder(root): | ||
| if prev_val is not None and node.val <= prev_val: | ||
| return False | ||
| prev_val = node.val | ||
| return True | ||
| ``` | ||
|
|
||
| 3, inorderでのiterative, 見ている方向をstackにつめる | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| def sort_node_val_inorder(node): | ||
| nodes_stack = [(root, ["left"])] | ||
|
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.
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. 確かに関数化するならそうしないとダメですね、ありがとうございます。 |
||
| inordered_val = [] | ||
|
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. inordered_valsで複数形ですかね |
||
| while nodes_stack: | ||
| node, next_direction_ref = nodes_stack[-1] | ||
| if not node or next_direction_ref[0] == "parent": | ||
| nodes_stack.pop() | ||
| continue | ||
| if next_direction_ref[0] == "left": | ||
| nodes_stack.append((node.left, ["left"])) | ||
| next_direction_ref[0] = "right" | ||
| continue | ||
| if next_direction_ref[0] == "right": | ||
| inordered_val.append(node.val) | ||
| nodes_stack.append((node.right, ["left"])) | ||
| next_direction_ref[0] = "parent" | ||
|
Comment on lines
+108
to
+118
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. parentを見るのあんまりしっくり気ませんでした。rightの時点で抜ければ良さそうな。 while nodes_stack:
node, next_direction_ref = nodes_stack[-1]
if not node:
nodes_stack.pop()
continue
if next_direction_ref[0] == "left":
nodes_stack.append((node.left, ["left"]))
next_direction_ref[0] = "right"
continue
if next_direction_ref[0] == "right":
inordered_vals.append(node.val)
nodes_stack.pop()
nodes_stack.append((node.right, ["left"]))
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. https://discord.com/channels/1084280443945353267/1227073733844406343/1236682759649497099 |
||
| return inordered_val | ||
|
|
||
| inordered_val = sort_node_val_inorder(root) | ||
| unique_count = len(set(inordered_val)) | ||
| return inordered_val == sorted(inordered_val) and len(inordered_val) == unique_count | ||
|
|
||
| ``` | ||
|
|
||
| 4, inorderでのiterative その2 | ||
|
|
||
| - なんかスッキリしないのでこの後さらにリファクタ | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| def check_valid(node): | ||
|
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. inner functionにする必要がないような?
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. rootという変数名が動くのが微妙だなあと思ったんですが、そこまで考慮する必要はなかったですかね 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. そこを気にするなら、そのrootという変数名をnodeに変更すれば良い気がしますね。
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. 確かにそうでした、、 |
||
| left_childs_stack = [] | ||
| prev_val = None | ||
| while node or left_childs_stack: | ||
| while node: | ||
| left_childs_stack.append(node) | ||
| node = node.left | ||
| node = left_childs_stack.pop() | ||
| if prev_val is not None and prev_val >= node.val: | ||
| return False | ||
| prev_val = node.val | ||
| node = node.right | ||
| return True | ||
|
|
||
| return check_valid(root) | ||
| ``` | ||
|
|
||
| 5, 上のやつはこの方がいいかも | ||
|
|
||
| - 処理として素直な気がする | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| def is_valid(node): | ||
|
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. 同じく、inner functionにしなくて良さそうです 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. inner にすることは、たとえば、その外側のローカル変数を使うとかで、せざるをえないことがありますが、そうでなければ、class method として並べちゃったほうが私は読みやすいと思いますね。入れ子にすると外側の関数が呼ばれるたびに内側が作られることになるので。
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. ありがとうございます、この辺の選択基準が自分の中になかったので参考になりました |
||
| def append_left_children_to_stack(node): | ||
| left_node = node | ||
| while left_node: | ||
| left_children_stack.append(left_node) | ||
| left_node = left_node.left | ||
|
|
||
| left_children_stack = [] | ||
| append_left_children_to_stack(node) | ||
| prev_node = TreeNode(-sys.maxsize) | ||
|
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. 個人的には |
||
| while True: | ||
|
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. left_children_stackが空でない間はループするの方が分かりやすくないでしょうか?🙇
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. ありがとうございます。 |
||
| if not left_children_stack: | ||
| return True | ||
| current_node = left_children_stack.pop() | ||
| if prev_node.val >= current_node.val: | ||
| return False | ||
| if current_node.right: | ||
| append_left_children_to_stack(current_node.right) | ||
| prev_node = current_node | ||
|
|
||
| return is_valid(root) | ||
| ``` | ||
|
|
||
| 6, Morris in-order([参考](https://discord.com/channels/1084280443945353267/1200089668901937312/1213356258103525407)) | ||
|
|
||
| - 理解に苦労 | ||
| - 基本的にはrightに行ってinorder順にたどろうとするが、まだ左が見れてないならばそっちを先に見る | ||
| - その時左にたどるついでに、今のnodeが後で.rightで戻れるようにつなげておく | ||
| - くらいの理解(むずい) | ||
|
Comment on lines
+186
to
+188
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. 高さ3くらいの適当な二分木を図に書いて、実際にtraverseしてみると分かるかもしれないです (自分はそれで納得しました)。要するに自分の右の子にinorder順で次のノードが常に来るように先にワープを作っておいて、ワープを実際に使った後は消して元に戻しておくんだと思います。
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. 絵を描いて実際シミュレーションしたら理解が深まりました! |
||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| def generate_node_inorder(node): | ||
| def search_previous_node_inorder(node): | ||
| if not node.left: | ||
| return None, True | ||
| prev = node.left | ||
| while prev.right: | ||
| if prev.right == node: | ||
| return prev, True | ||
| prev = prev.right | ||
| return prev, False | ||
|
|
||
| while node: | ||
| prev, left_searched = search_previous_node_inorder(node) | ||
| if not prev: | ||
| yield node | ||
| node = node.right | ||
| continue | ||
| if not left_searched: | ||
| prev.right = node | ||
| node = node.left | ||
| continue | ||
| yield node | ||
| node = node.right | ||
| prev.right = None | ||
|
|
||
| prev_val = None | ||
| for node in generate_node_inorder(root): | ||
| if prev_val is not None and prev_val >= node.val: | ||
| return False | ||
| prev_val = node.val | ||
| return True | ||
| ``` | ||
|
|
||
| ### Step3 | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
| def generate_node_inorder(node): | ||
| if not node: | ||
| return | ||
| yield from generate_node_inorder(node.left) | ||
| yield node | ||
| yield from generate_node_inorder(node.right) | ||
|
|
||
| prev_val = None | ||
| for node in generate_node_inorder(root): | ||
| if prev_val is not None and prev_val >= node.val: | ||
| return False | ||
| prev_val = node.val | ||
| return True | ||
| ``` | ||
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.
left_min, left_max, left_validがそれぞれどのように更新されるのか追う必要があり、step2に比べて追うのが辛いと感じました。step2でも3つの引数を関数に渡ししていますが、関数に渡すタイミングで引数の内2つはvalueが取りうる値のレンジだと読み取ることができたので読みやすいと感じました。個人的な意見になります🙇