From c341c7a865e9db706a65902c85c6b051fcd76827 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 10 Nov 2025 18:13:44 +0900 Subject: [PATCH 1/3] Solve 98_validate_binary_search_tree_medium --- .../README.md | 90 +++++++++++++++++++ .../iterative.py | 29 ++++++ .../step1.py | 33 +++++++ .../step2.py | 28 ++++++ .../step3.py | 20 +++++ 5 files changed, 200 insertions(+) create mode 100644 98_validate_binary_search_tree_medium/README.md create mode 100644 98_validate_binary_search_tree_medium/iterative.py create mode 100644 98_validate_binary_search_tree_medium/step1.py create mode 100644 98_validate_binary_search_tree_medium/step2.py create mode 100644 98_validate_binary_search_tree_medium/step3.py diff --git a/98_validate_binary_search_tree_medium/README.md b/98_validate_binary_search_tree_medium/README.md new file mode 100644 index 0000000..2f5732a --- /dev/null +++ b/98_validate_binary_search_tree_medium/README.md @@ -0,0 +1,90 @@ +# 問題へのリンク +[98. Validate Binary Search Tree](https://leetcode.com/problems/validate-binary-search-tree/) + +# 言語 +Python + + +# 自分の解法 + +## step1 + +```python +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + if root is None: + return True + def find_validity_and_minmax(root: TreeNode) -> tuple[bool, int, int]: + min_val = max_val = root.val + if root.left is not None: + is_left_valid, min_val, left_max = find_validity_and_minmax(root.left) + if not is_left_valid or left_max >= root.val: + return False, 0, 0 + if root.right is not None: + is_right_valid, right_min, max_val = find_validity_and_minmax(root.right) + if not is_right_valid or root.val >= right_min: + return False, 0, 0 + return True, min_val, max_val + is_valid, _, _ = find_validity_and_minmax(root) + return is_valid +``` + +`n`をノード数とすると、 +- 時間計算量:`O(n)` +- 空間計算量:`O(n)` + +テストケース +```python +root = [2,1,3] -> True +root = [1] -> True +root = [2, 1, 3] -> True +root = [2, null, 3] -> True +root = [2, 1, null] -> True +root = [1, 1, 2] -> False +root = [2, 1, 2] -> False +``` +- 「各subtreeがBSTか」、「subtreeの最小値・最大値」を再帰的に取得し、rootの値と比較することでBSTかどうかを判定している。 +- が、関数として自然でない上に、コードが冗長になってしまっている。 +- step2以降の`lower`/`upper`を用いた方法の方がシンプルに実装できる。 + +## step2 + +```python +import math + +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + def is_valid(root, lower, upper): + if not root: + return True + if root.val <= lower or root.val >= upper: + return False + return is_valid(root.left, lower, root.val) and is_valid(root.right, root.val, upper) + + return is_valid(root, -math.inf, math.inf) +``` + +- 「rootが、左のsubtreeの最大値より大きく、右のsubtreeの最小値より小さい」という条件を、「左のsubtreeのノードが全てroot.valより小さい、右のsubtreeのノードが全てroot.valより大きい」と言い換えれば、よりシンプルに実装できる。 + +## step3 + +## step4 (FB) + + + +# 別解・模範解答 + +- 時間計算量:`O(n)` +- 空間計算量:`O(n)` + + + +# 想定されるフォローアップ質問 + +## CS 基礎 + +## システム設計 + +## その他 + +# 次に解く問題の予告 diff --git a/98_validate_binary_search_tree_medium/iterative.py b/98_validate_binary_search_tree_medium/iterative.py new file mode 100644 index 0000000..d5d42b9 --- /dev/null +++ b/98_validate_binary_search_tree_medium/iterative.py @@ -0,0 +1,29 @@ +# +# @lc app=leetcode id=98 lang=python3 +# +# [98] Validate Binary Search Tree +# + +# @lc code=start +# 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 + +import math +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + stack = [(root, -math.inf, math.inf)] + while stack: + root, lower, upper = stack.pop() + if root is None: + continue + if lower >= root.val or upper <= root.val: + return False + stack.append((root.left, lower, root.val)) + stack.append((root.right, root.val, upper)) + return True + +# @lc code=end diff --git a/98_validate_binary_search_tree_medium/step1.py b/98_validate_binary_search_tree_medium/step1.py new file mode 100644 index 0000000..b43c0d5 --- /dev/null +++ b/98_validate_binary_search_tree_medium/step1.py @@ -0,0 +1,33 @@ +# +# @lc app=leetcode id=98 lang=python3 +# +# [98] Validate Binary Search Tree +# + +# @lc code=start +# 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: TreeNode|None) -> bool: + if root is None: + return True + def find_validity_and_minmax(root: TreeNode) -> tuple[bool, int, int]: + min_val = max_val = root.val + if root.left is not None: + is_left_valid, min_val, left_max = find_validity_and_minmax(root.left) + if not is_left_valid or left_max >= root.val: + return False, 0, 0 + if root.right is not None: + is_right_valid, right_min, max_val = find_validity_and_minmax(root.right) + if not is_right_valid or root.val >= right_min: + return False, 0, 0 + return True, min_val, max_val + is_valid, _, _ = find_validity_and_minmax(root) + return is_valid + +# @lc code=end diff --git a/98_validate_binary_search_tree_medium/step2.py b/98_validate_binary_search_tree_medium/step2.py new file mode 100644 index 0000000..0d9b976 --- /dev/null +++ b/98_validate_binary_search_tree_medium/step2.py @@ -0,0 +1,28 @@ +# +# @lc app=leetcode id=98 lang=python3 +# +# [98] Validate Binary Search Tree +# + +# @lc code=start +# 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 + +import math + +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + def is_valid(root, lower, upper): + if not root: + return True + if root.val <= lower or root.val >= upper: + return False + return is_valid(root.left, lower, root.val) and is_valid(root.right, root.val, upper) + + return is_valid(root, -math.inf, math.inf) + +# @lc code=end diff --git a/98_validate_binary_search_tree_medium/step3.py b/98_validate_binary_search_tree_medium/step3.py new file mode 100644 index 0000000..60df534 --- /dev/null +++ b/98_validate_binary_search_tree_medium/step3.py @@ -0,0 +1,20 @@ +# +# @lc app=leetcode id=98 lang=python3 +# +# [98] Validate Binary Search Tree +# + +# @lc code=start +# 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: TreeNode|None) -> bool: + return False + +# @lc code=end From 1db3424e8eb1eb971f4dd393c3539b1823e4569e Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 10 Nov 2025 22:15:48 +0900 Subject: [PATCH 2/3] Add iterative solutions for validating a binary search tree --- .../README.md | 93 ++++++++++++++++--- .../iterative_fifo.py | 31 +++++++ .../{iterative.py => iterative_lifo.py} | 15 +-- 3 files changed, 117 insertions(+), 22 deletions(-) create mode 100644 98_validate_binary_search_tree_medium/iterative_fifo.py rename 98_validate_binary_search_tree_medium/{iterative.py => iterative_lifo.py} (58%) diff --git a/98_validate_binary_search_tree_medium/README.md b/98_validate_binary_search_tree_medium/README.md index 2f5732a..c8d6c3d 100644 --- a/98_validate_binary_search_tree_medium/README.md +++ b/98_validate_binary_search_tree_medium/README.md @@ -32,21 +32,41 @@ class Solution: `n`をノード数とすると、 - 時間計算量:`O(n)` - 空間計算量:`O(n)` + - もっと厳密には木の高さを`h`とすると`O(h)`。ただし、最悪の場合`h = n`。 + - 再帰呼び出しのスタックが`h`深くなるため。 + - 平衡木の場合は`O(log n)`。 テストケース -```python -root = [2,1,3] -> True -root = [1] -> True -root = [2, 1, 3] -> True -root = [2, null, 3] -> True -root = [2, 1, null] -> True -root = [1, 1, 2] -> False -root = [2, 1, 2] -> False +- 標準的なBSTケース:`root = [2,1,3]` -> `True` +- 標準的なBSTでないケース:`root = [5,1,4,null,null,3,6]` -> `False` +- 空の木:`root = []` -> `True` +- 単一ノード:`root = [1]` -> `True` +- 右に偏った木:`root = [2, null, 3]` -> `True` +- 左に偏った木:`root = [2, 1, null]` -> `True` +- 境界ケース:`root = [1, 1, 2]` -> `False`、`root = [2, 1, 2]` -> `False` + +木をテキストとして書く方法 +``` + 1 + / \ +2 3 + \ + 4 ``` + + - 「各subtreeがBSTか」、「subtreeの最小値・最大値」を再帰的に取得し、rootの値と比較することでBSTかどうかを判定している。 -- が、関数として自然でない上に、コードが冗長になってしまっている。 + - が、関数として自然でない上に、コードが冗長になってしまっている。 + - これは考え方としては、左右のsubtreeをgivenとして、中央にrootを置いたらそれはBSTか、という形で考えている。 + - 別の考え方は、rootから順にノードを置いていく。rootをgivenとして、左(右)のsubtreeのノードを配置していく時には、「root.valより小さい(大きい)」という制約が課されるという考え方。 - step2以降の`lower`/`upper`を用いた方法の方がシンプルに実装できる。 + +二分木の走査方法としては、以下の3つがある。 +- Inorder (left -> root -> right) +- Preorder (root -> left -> right) +- Postorder (left -> right -> root) + ## step2 ```python @@ -73,18 +93,61 @@ class Solution: # 別解・模範解答 +## 反復的な解法 -- 時間計算量:`O(n)` -- 空間計算量:`O(n)` +rootから始めて、左右に進む際に、`lower`/`upper`の制約を更新していく。 +ノードをpushした時点で、そのノードに対する制約(`lower`/`upper`)は確定するので、ノードを取り出す順序はなんでも良い(スタックでもキューでもなんでも良い)。 +`iterative_lifo.py` +```python +import math -# 想定されるフォローアップ質問 +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + frontiers = [(root, -math.inf, math.inf)] + while frontiers: + node, lower, upper = frontiers.pop() + if node is None: + continue + if lower >= node.val or upper <= node.val: + return False + frontiers.append((node.left, lower, node.val)) + frontiers.append((node.right, node.val, upper)) + return True +``` + + +`iterative_fifo.py` +```python +import math +from collections import deque + +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + frontiers = deque([(root, -math.inf, math.inf)]) + while frontiers: + node, lower, upper = frontiers.popleft() + if node is None: + continue + if not (lower < node.val < upper): + return False + frontiers.append((node.left, lower, node.val)) + frontiers.append((node.right, node.val, upper)) + return True +``` + +- 時間計算量:`O(n)` +- 空間計算量:`O(n)` + - 今度は、木が非常に偏っている場合は`O(1)`になるが、平衡木の場合は`O(n)`になる。 + - スタック/キューにノードが最大で`n/2`個入る可能性があるため。 -## CS 基礎 -## システム設計 +# 想定されるフォローアップ質問 -## その他 +- Q. 再帰的な解法と反復的な解法の違い、使い分けは? + - A. 再帰的な解法はコードがシンプルになる一方で、再帰の深さの分だけメモリを消費する。この問題では、木が偏っている場合には、再帰的な解法はスタックオーバーフローのリスクがあるため、反復的な解法が好まれる場合がある。一方、木が平衡に保たれている場合、反復的な解法よりも再帰的な解法の方が空間計算量が小さくなる。 # 次に解く問題の予告 +- [Word Ladder - LeetCode](https://leetcode.com/problems/word-ladder/description/) +- [Top K Frequent Elements - LeetCode](https://leetcode.com/problems/top-k-frequent-elements/description/) diff --git a/98_validate_binary_search_tree_medium/iterative_fifo.py b/98_validate_binary_search_tree_medium/iterative_fifo.py new file mode 100644 index 0000000..3a72244 --- /dev/null +++ b/98_validate_binary_search_tree_medium/iterative_fifo.py @@ -0,0 +1,31 @@ +# +# @lc app=leetcode id=98 lang=python3 +# +# [98] Validate Binary Search Tree +# + +# @lc code=start +# 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 + +import math +from collections import deque + +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + frontiers = deque([(root, -math.inf, math.inf)]) + while frontiers: + node, lower, upper = frontiers.popleft() + if node is None: + continue + if not (lower < node.val < upper): + return False + frontiers.append((node.left, lower, node.val)) + frontiers.append((node.right, node.val, upper)) + return True + +# @lc code=end diff --git a/98_validate_binary_search_tree_medium/iterative.py b/98_validate_binary_search_tree_medium/iterative_lifo.py similarity index 58% rename from 98_validate_binary_search_tree_medium/iterative.py rename to 98_validate_binary_search_tree_medium/iterative_lifo.py index d5d42b9..88f06d8 100644 --- a/98_validate_binary_search_tree_medium/iterative.py +++ b/98_validate_binary_search_tree_medium/iterative_lifo.py @@ -13,17 +13,18 @@ def __init__(self, val=0, left=None, right=None): self.right = right import math + class Solution: def isValidBST(self, root: TreeNode|None) -> bool: - stack = [(root, -math.inf, math.inf)] - while stack: - root, lower, upper = stack.pop() - if root is None: + frontiers = [(root, -math.inf, math.inf)] + while frontiers: + node, lower, upper = frontiers.pop() + if node is None: continue - if lower >= root.val or upper <= root.val: + if lower >= node.val or upper <= node.val: return False - stack.append((root.left, lower, root.val)) - stack.append((root.right, root.val, upper)) + frontiers.append((node.left, lower, node.val)) + frontiers.append((node.right, node.val, upper)) return True # @lc code=end From 01772714e415f3f201a2d9ddd1768d457040c696 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 26 Nov 2025 18:43:54 +0900 Subject: [PATCH 3/3] Add step3 --- .../README.md | 20 +++++++++++++++++++ .../step3.py | 13 ++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/98_validate_binary_search_tree_medium/README.md b/98_validate_binary_search_tree_medium/README.md index c8d6c3d..822f790 100644 --- a/98_validate_binary_search_tree_medium/README.md +++ b/98_validate_binary_search_tree_medium/README.md @@ -88,6 +88,26 @@ class Solution: ## step3 +```python +import math +class Solution: + def isValidBST(self, root: TreeNode|None) -> bool: + def is_valid(root, lower_bound, upper_bound) -> bool: + if root is None: + return True + if not (lower_bound < root.val < upper_bound): + return False + left_validity = is_valid(root.left, lower_bound, root.val) + right_validity = is_valid(root.right, root.val, upper_bound) + return left_validity and right_validity + + return is_valid(root, -math.inf, math.inf) +``` + +- `lower`/`upper`を`lower_bound`/`upper_bound`に変えた +- 条件式を`not (lower < root.val < upper)`に変えた。この方が読みやすいと思ったため。 + + ## step4 (FB) diff --git a/98_validate_binary_search_tree_medium/step3.py b/98_validate_binary_search_tree_medium/step3.py index 60df534..9bd20c8 100644 --- a/98_validate_binary_search_tree_medium/step3.py +++ b/98_validate_binary_search_tree_medium/step3.py @@ -12,9 +12,18 @@ def __init__(self, val=0, left=None, right=None): self.left = left self.right = right - +import math class Solution: def isValidBST(self, root: TreeNode|None) -> bool: - return False + def is_valid(root, lower_bound, upper_bound) -> bool: + if root is None: + return True + if not (lower_bound < root.val < upper_bound): + return False + left_validity = is_valid(root.left, lower_bound, root.val) + right_validity = is_valid(root.right, root.val, upper_bound) + return left_validity and right_validity + + return is_valid(root, -math.inf, math.inf) # @lc code=end