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
47 changes: 47 additions & 0 deletions validate_binary_tree/phase1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

# テストケースで親ノードが及ぼす制約を考慮できていなく弾かれる
# なんだか命名やら条件文の書き方が不安
# 空間計算量と時間計算量ともにノード数をNとしたときにO(N)
# 今回の最大ノード数は10^4なのでPythonの1秒間の実行数が10^7程度だった記憶があるので(間違ってたら指摘してください)10^-3秒程度で実行が終わる
# そういえば, 今回はスタックオーバーフローが起こらなかったが, Pythonのデフォルトでの最大再帰数を考えると最大ノード数は10^4なので片方に偏ったBinary Treeの場合再帰エラーがおきる

class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
# -2 ** 31 <= Node.val <= 2 ** 31 - 1なので開区間でとる
MIN = -2 ** 31 - 1
MAX = 2 ** 32
Copy link

Choose a reason for hiding this comment

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

infでいいのではないでしょうか?

Copy link
Owner Author

Choose a reason for hiding this comment

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

fhiyo/leetcode#41 (comment)

この辺の会話を思い出しており, 型がfloat型なのでmath.infを使いたくなかったというのが正直なところです。どのみちマジックナンバーであまりよくはないのですが...

Copy link

Choose a reason for hiding this comment

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

なるほどです。自分ならPythonは型の柔軟性が高いので気にせず使っちゃってコード量の削減を優先するかなと思いました。OptionalだとNoneの確認が面倒なので
好みの問題だとは思います。

Copy link

Choose a reason for hiding this comment

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

このMAXの2^32は、2^31の間違いですか?

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 self.isValidHelper(root, MIN, MAX)

def isValidHelper(self, node: Optional[TreeNode], left, right) -> bool:
Copy link

Choose a reason for hiding this comment

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

node.left と left が違う型のものなのがわりと混乱する感じがします。

# left, rightは子に対する制約 left < node.child.val < rightであることを課す
# left, rightは親の値によってきまる

if node.left and node.right:
Copy link

Choose a reason for hiding this comment

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

子の値を親が確認している点にやや違和感を感じました。この関数は、 node.val のみをチェックし、子の val のチェックは再帰関数の呼び出し先で行うのが良いと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

子の値を親が確認しないように変更したものをphase4にupいたしました。

if not (left < node.left.val < right and left < node.right.val < right):
return False
if node.left.val < node.val < node.right.val:
return self.isValidHelper(node.left, left, node.val) and self.isValidHelper(node.right, node.val, right)
return False

if node.left:
if not left < node.left.val < right:
return False
if node.left.val < node.val:
return self.isValidHelper(node.left, left, node.val)
return False

if node.right:
if not left < node.right.val < right:
return False
if node.val < node.right.val:
return self.isValidHelper(node.right, node.val, right)
return False

# ここまで到達すると葉ノードであることを示す
return True
66 changes: 66 additions & 0 deletions validate_binary_tree/phase2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# hroc135: https://github.com/hroc135/leetcode/pull/27/files
# lower_bound, upper_boundとして名前を採用, 末尾最適化の話を一応確認, やはりMIN, MAXのマジックナンバーはよくなかったかと思い, NULLの時も考慮した比較関数isWithinIntervalを用意
# isValidChildなども名前の候補にあがったけどisValidHelperと紛らわしくタイポが発生したのでやめた
# goto-untrapped: https://github.com/goto-untrapped/Arai60/pull/52/files
# fhiyo: https://github.com/fhiyo/leetcode/pull/30/files
# inorderでやる方法, アルゴリズムイントロダクションでもやったので真っ先に思い浮かんだが親のポインタがないので面倒だなと思っていたがyieldを使えばいいことを学ぶ
# あとはnode.valをif文の左側にもってくるなどの修正を加えるなど

Copy link

Choose a reason for hiding this comment

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

下から上に再帰するのもありかもしれませんね。
あるノードの下のノードの val の範囲、そこから下が valid かどうか、が求められる関数があれば、再帰できそうです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

phase5に解いてみました

class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
return self.isValidHelper(root, None, None)

def isValidHelper(self, node: Optional[TreeNode], lower_bound: Optional[int], upper_bound: Optional[int]) -> bool:
# lower_bound, upper_boundは子に対する制約 lower_bound <= node.child.val <= upper_boundであることを課す
# lower_bound, upper_boundは親の値によってきまる
if node.left and node.right:
if not self.isWithinInterval(node.left, lower_bound, upper_bound) or not self.isWithinInterval(node.right, lower_bound, upper_bound):
return False
if node.left.val < node.val < node.right.val:
return self.isValidHelper(node.left, lower_bound, node.val) and self.isValidHelper(node.right, node.val, upper_bound)
return False

if node.left:
if not self.isWithinInterval(node.left, lower_bound, upper_bound):
return False
if node.val > node.left.val:
return self.isValidHelper(node.left, lower_bound, node.val)
return False

if node.right:
if not self.isWithinInterval(node.right, lower_bound, upper_bound):
return False
if node.val < node.right.val:
return self.isValidHelper(node.right, node.val, upper_bound)
return False
# ここまで到達すると葉ノードであることを示す
return True

def isWithinInterval(self, node: TreeNode, lower_bound: Optional[int], upper_bound: Optional[int]) -> bool:
if lower_bound and upper_bound:
return lower_bound < node.val < upper_bound

if lower_bound:
return node.val > lower_bound

if upper_bound:
return node.val < upper_bound
# root nodeの場合
return True

# yieldを使ったinorder
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)
# inorderで探索してソートされていなかったら二分木ではない
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
38 changes: 38 additions & 0 deletions validate_binary_tree/phase3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
return self.isValidBSTHelper(root, None, None)

def isValidBSTHelper(self, node: Optional[TreeNode], lower_bound: Optional[TreeNode], upper_bound: Optional[TreeNode]) -> bool:
if node.left and node.right:
if not self.isWithinIntervals(node.left, lower_bound, upper_bound) or not self.isWithinIntervals(node.right, lower_bound, upper_bound):
return False
if node.left.val < node.val < node.right.val:
return self.isValidBSTHelper(node.left, lower_bound, node.val) and self.isValidBSTHelper(node.right, node.val, upper_bound)
return False
if node.left:
if not self.isWithinIntervals(node.left, lower_bound, upper_bound):
return False
if node.val > node.left.val:
return self.isValidBSTHelper(node.left, lower_bound, node.val)
return False

if node.right:
if not self.isWithinIntervals(node.right, lower_bound, upper_bound):
return False
if node.val < node.right.val:
return self.isValidBSTHelper(node.right, node.val, upper_bound)
return False
# 葉ノードの場合
return True

def isWithinIntervals(self, node: TreeNode, lower_bound: Optional[TreeNode], upper_bound: Optional[TreeNode]) -> bool:
if lower_bound and upper_bound:
return lower_bound < node.val < upper_bound

if lower_bound:
return node.val > lower_bound

if upper_bound:
return node.val < upper_bound

return True
30 changes: 30 additions & 0 deletions validate_binary_tree/phase4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
return self.isValidBSTHelper(root, None, None)

def isValidBSTHelper(self, node: Optional[TreeNode], lower_bound: Optional[int], upper_bound: Optional[int]) -> bool:
if not node:
return True

if not self.isWithinIntervals(node, lower_bound, upper_bound):
return False

return self.isValidBSTHelper(node.left, lower_bound, node.val) and self.isValidBSTHelper(node.right, node.val, upper_bound)
Copy link

Choose a reason for hiding this comment

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

自分なら

if not self.isValidBSTHelper(node.left, lower_bound, node.val):
    return False
if not self.isValidBSTHelper(node.right, node.val, upper_bound):
    return False
return True

とします。


def isWithinIntervals(self, node: TreeNode, lower_bound: Optional[int], upper_bound: Optional[int]) -> bool:
Copy link

Choose a reason for hiding this comment

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

_isWithinIntervals, _isValidBSTHelperとしてプライベート関数として定義するといいと思いました

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。是非そうさせていただきます🙇

if lower_bound and upper_bound:
Copy link

Choose a reason for hiding this comment

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

自分だったら最初に、lower_bound is None and upper_bound is None return Trueを書きます(特別な処理を先に書きたい)

return lower_bound < node.val < upper_bound

if lower_bound is not None:
return node.val > lower_bound

if upper_bound is not None:
return node.val < upper_bound

return True
31 changes: 31 additions & 0 deletions validate_binary_tree/phase5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
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のままなのが少し嫌な気がする

if not root:
return True

left_max, _ = self.subtreeMinMax(root.left)
_, right_min = self.subtreeMinMax(root.right)

if left_max is not None and root.val <= left_max:
return False
if right_min is not None and root.val >= right_min:
return False

return self.isValidBST(root.left) and self.isValidBST(root.right)

@cache
Copy link

Choose a reason for hiding this comment

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

ここにキャッシュがついていると、上の isValidBST を2回呼んだ時、つまり、木を変更してもう一回呼んだときに、TreeNode の id でキャッシュされると思われるので、木の先のほうが変更されていても、同じ値が返るという意味でうまくいかないのではないでしょうか。

なので、isValidBST をヘルパー関数に変更して、インナーファンクションとして isValidBST と subtreeMinMax を書いたほうがいいのではないでしょうかね。

def subtreeMinMax(self, node: Optional[TreeNode]) -> Tuple[Optional[int], Optional[int]]:
if not node:
return None, None

left_max, left_min = self.subtreeMinMax(node.left)
right_max, right_min = self.subtreeMinMax(node.right)

if left_max is not None and right_max is not None:
return max(node.val, left_max, right_max), min(node.val, left_min, right_min)
if left_max is not None:
return max(node.val, left_max), min(node.val, left_min)
if right_max is not None:
return max(node.val, right_max), min(node.val, right_min)

return node.val, node.val