Skip to content

Conversation

@nittoco
Copy link
Owner

@nittoco nittoco commented Aug 28, 2024

https://leetcode.com/problems/validate-binary-search-tree/description/

Given the root of a binary tree, determine if it is a valid binary search tree (BST).

A valid BST is defined as follows:

The left subtree of a node contains only nodes with keys less than the node's key.
The right subtree of a node contains only nodes with keys greater than the node's key. Both the left and right subtrees must also be binary search trees.

Given the root of a binary tree, determine if it is a valid binary search tree (BST).

A valid BST is defined as follows:

The left 
subtree
 of a node contains only nodes with keys less than the node's key.
The right subtree of a node contains only nodes with keys greater than the node's key.
Both the left and right subtrees must also be binary search trees.
@nittoco nittoco changed the title validate binary seach tree 98. validate binary seach tree Aug 28, 2024
Copy link

@fhiyo fhiyo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントはたくさんしましたが、良いと思いました!
とても勉強にもなりました、ありがとうございます

Comment on lines +186 to +188
- 基本的にはrightに行ってinorder順にたどろうとするが、まだ左が見れてないならばそっちを先に見る
- その時左にたどるついでに、今のnodeが後で.rightで戻れるようにつなげておく
- くらいの理解(むずい)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

高さ3くらいの適当な二分木を図に書いて、実際にtraverseしてみると分かるかもしれないです (自分はそれで納得しました)。要するに自分の右の子にinorder順で次のノードが常に来るように先にワープを作っておいて、ワープを実際に使った後は消して元に戻しておくんだと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

絵を描いて実際シミュレーションしたら理解が深まりました!
ありがとうございます!


class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def calculate_min_max_and_check_valid(node):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

クラスかnamedtupleかdataclass辺りを作って返した方が分かりやすいかもしれません。いい名前が思いつかないですが...
(自分はTreeStatusと付けましたがうーん...という感じです)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namedtuple, dataclass共、名前は聞いたことあるけどこういう時使うんだなあと思いました。
ドキュメントとか読んで調べてみます。ありがとうございます

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上の行の続きならそれと分かるようにスペースをいくつか入れて行の先頭がずれるようにした方が良い気がします。

Comment on lines +108 to +118
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"
Copy link

Choose a reason for hiding this comment

The 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"]))

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://discord.com/channels/1084280443945353267/1227073733844406343/1236682759649497099
これを書いた時の気持ちを思い出していたんですが、この辺の行きと帰りをstackに入れるとかを考えながらやってました。ただ、この場合は帰りになんか処理してるわけじゃないので意味なかったですね。
sort_inorder_val(node.left)
inorder_vals.append(node.val)
sort_inorder_val(node.right)
の処理順を素直にstackに書き換えるとfhiyoさんのコードになりますね。
理解深まりました。ありがとうございます

def isValidBST(self, root: Optional[TreeNode]) -> bool:
def sort_node_val_inorder(node):
nodes_stack = [(root, ["left"])]
inordered_val = []
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inordered_valsで複数形ですかね


class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def check_valid(node):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inner functionにする必要がないような?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rootという変数名が動くのが微妙だなあと思ったんですが、そこまで考慮する必要はなかったですかね

Copy link

@fhiyo fhiyo Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そこを気にするなら、そのrootという変数名をnodeに変更すれば良い気がしますね。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かにそうでした、、


class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def is_valid(node):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同じく、inner functionにしなくて良さそうです

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inner にすることは、たとえば、その外側のローカル変数を使うとかで、せざるをえないことがありますが、そうでなければ、class method として並べちゃったほうが私は読みやすいと思いますね。入れ子にすると外側の関数が呼ばれるたびに内側が作られることになるので。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、この辺の選択基準が自分の中になかったので参考になりました


left_children_stack = []
append_left_children_to_stack(node)
prev_node = TreeNode(-sys.maxsize)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には -sys.maxsize より小さい値は来ないのか不安になります。コメントでそういう仮定を置いている (実務ならその仮定がある程度妥当な) ことを書いておいてほしい気がします。

## Step2

- [探索順自体をin-orderにする方法もあった](https://github.com/TORUS0818/leetcode/pull/30/files)
- あと、math.infがマジックナンバーぽいという意見もあった。すぐわかるものだと思っていた。

Choose a reason for hiding this comment

The 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]みたいな方が安心するかもしれません。
あるいは、min/maxという表現ではなくて、inf/supという表現であれば正しいと思います。(ただ、任意の同僚に対してinf/supがすぐ通じるかというと、微妙かも?)
https://en.wikipedia.org/wiki/Infimum_and_supremum

(あと、pythonだとvalの型が明示されていないですが、他の言語を選択するとvalの型はintとかIntとかなので、infはfloatみたいな型の話もあるかもしれません。)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
確かにmin, maxでinfが帰ってくると変ですね

left_children_stack = []
append_left_children_to_stack(node)
prev_node = TreeNode(-sys.maxsize)
while True:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left_children_stackが空でない間はループするの方が分かりやすくないでしょうか?🙇

while left_children_stack:  
                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 True

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
これは正直今も迷ってます。(早期にreturn Trueする方がその条件がわかりやすいか迷いました)


- 本当はFalseが来た時にすぐ返したい。(いちいちmin, maxを計算するのも大変)nonlocalの方がよかったか
- でもnonlocalはnonlocalで実装がちょっと見にくいかな
- returnする値も多い気もする

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が取りうる値のレンジだと読み取ることができたので読みやすいと感じました。個人的な意見になります🙇


2, inorderでの再帰 yield fromの利用

- generatorにすればfor文で回せるので、単純なreturnより前との比較が楽
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generator + 再帰、強力ですね。pros cons の cons としては、再帰の深さの限界があること、走りかけの Generator を木の深さ分だけ作るのでそこそこ重いことがありそうですね。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consのところいまいちわかってなかったので参考になりました。ありがとうございます。

Comment on lines +17 to +18
def is_current_node_valid(node):
return left_max < node.val < right_min

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これも中で定義しない方がいいかもですね。

- でもnonlocalはnonlocalで実装がちょっと見にくいかな
- returnする値も多い気もする
- 引数が多いのと、returnする値が多いのと、どちらの方がいいのか、選択の基準を持てていない
- calculate_min_max_and_check_validは一つの関数で2つの動作、役割があり、微妙な気もするが、しょうがない?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この点でもstep2の方が自然なのかもしれないですね。

class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def sort_node_val_inorder(node):
nodes_stack = [(root, ["left"])]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodes_stack = [(node, ["left"])]でしょうか?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かに関数化するならそうしないとダメですね、ありがとうございます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants