-
Notifications
You must be signed in to change notification settings - Fork 0
Create 0776-split-bst.md #48
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
docto-rin
wants to merge
1
commit into
main
Choose a base branch
from
0776-split-bst
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
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,150 @@ | ||
| ## Step 1 | ||
|
|
||
| - 問題文(LINTCODE版) | ||
| - 二分探索木(BST)の根`root`とこのBSTを2つの部分木に分割するためのターゲット値`v`が与えられる。 | ||
| - 2分割される部分木のうち、一方は`v`以下の値を持つノード、他方は`v`より大きい値を持つノードから構成される。 | ||
| - 元の木`root`は必ずしも`v`に等しい値を持つノードを持つ必要はない。 | ||
| - さらに、元の木`root`の大部分の構造は保持されるべきである。 | ||
| - 簡単に言えば、元の木において親ノードPを持つ任意の子ノードCについて、分割後も両者が同じ部分木内にある場合、ノードCは依然としてPの子ノードであるべきである。 | ||
| - この分割された2つの部分木のうち、ノード数の多い方を返せ。 | ||
| - もしノード数が同じである場合は、根の値が大きい方を返せ。 | ||
| - 制約: | ||
| - The size of the BST will not exceed 50 | ||
| - The BST is always valid and each node's value is different | ||
|
|
||
| ### 実装1 | ||
|
|
||
| - アルゴリズムの選択 | ||
| - 説明のため、`v`以下の値からなる部分木と`v`より大きい値からなる部分木を、左部分木、右部分木と呼ぶことにする。 | ||
| - 分割地点自体は、BSTを活かしてTreeNode版の二分探索で容易に特定できる。 | ||
| - 難関は、つなげかえが必要になる場合と、返す部分木をどちらにするかの判断方法。 | ||
| - divide and conquer、つまりtop-down的な解決を目指す。 | ||
| - 実装 | ||
| - 再帰関数か、bottom-up化してiterativeだが、前者でさえも実装難易度が高いので前者で済ませる。 | ||
| - ヘルパー再帰関数を定義する。根ノードを入力とし、左部分木と右部分木を返り値とする。 | ||
| - このとき、再帰関数内で帰りがけの作業としてつなげかえを行う。 | ||
| - ヘルパー再帰関数の外側で2つの部分木のノード数のカウンタ変数を定義し、nonlocal宣言して更新する。 | ||
| - これ以外には、更新値を返り値にするか、クラス属性にするなどがある。 | ||
| - 返り値を増やすのは可読性が下がるのでやりたくない。クラス属性は、今回は他にメソッドを定義しないので見送る。 | ||
| - 計算量 | ||
| - Time: O(n) | ||
| - Space: O(depth) = O(n) | ||
|
|
||
| ```python3 | ||
| from lintcode import ( | ||
| TreeNode, | ||
| ) | ||
|
|
||
| """ | ||
| Definition of TreeNode: | ||
| class TreeNode: | ||
| def __init__(self, val): | ||
| self.val = val | ||
| self.left, self.right = None, None | ||
| """ | ||
|
|
||
| class Solution: | ||
| """ | ||
| @param root: the given tree | ||
| @param v: the target value | ||
| @return: the root TreeNode after splitting | ||
| """ | ||
| def split_b_s_t(self, root: TreeNode, v: int) -> TreeNode: | ||
| """Split a BST by value v and return the subtree with more nodes. | ||
| Note that this method is destructive to the original tree pointers. | ||
| """ | ||
| left_size = 0 | ||
| right_size = 0 | ||
|
|
||
| def split_bst_aux(node): | ||
| nonlocal left_size | ||
| nonlocal right_size | ||
|
|
||
| if node is None: | ||
| return None, None | ||
|
|
||
| if node.val <= v: | ||
| left_size += 1 | ||
| left_root, right_root = split_bst_aux(node.right) | ||
| split_bst_aux(node.left) | ||
| node.right = left_root | ||
| return node, right_root | ||
|
|
||
| right_size += 1 | ||
| left_root, right_root = split_bst_aux(node.left) | ||
| split_bst_aux(node.right) | ||
| node.left = right_root | ||
| return left_root, node | ||
|
|
||
| left_root, right_root = split_bst_aux(root) | ||
|
|
||
| if left_size > right_size: | ||
| return left_root | ||
| if left_size < right_size: | ||
| return right_root | ||
| if left_root.val > right_root.val: | ||
| return left_root | ||
| return right_root | ||
| ``` | ||
|
|
||
| - 30分くらいかかった。 | ||
| - 書き始める前、繋ぎかえの一般化ができず苦しんだ。 | ||
| - 思いつき、書き始めてからはスムーズに書けた。 | ||
| - 1回誤答した。単一の部分木内と確定した側も再帰関数を呼ばないと、部分木のサイズが正しく測れない。(再帰関数の返り値を捨てている行のことです。) | ||
|
|
||
| ## Step 2 | ||
|
|
||
| - [コメント集](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.3czeid3ovy2a) | ||
| - https://github.com/TORUS0818/leetcode/pull/49/ | ||
| - 分割(ポインタのつなげかえ)の走査とカウントの走査を分けている。この方が可読性が高い気もする。 | ||
| - > 必ず large_root.val のほうが大きくなると思います。 | ||
| - 見落としていた。 | ||
| - https://github.com/Mike0121/LeetCode/pull/16 | ||
| - > 左右の対称性をソースコードでも示すため、こちらのように if else の形で書いたほうが良いと思います。 | ||
| - これは書いてみたら確かに読みやすかった。常にif-continueにしたらいいわけじゃないことを学んだ。 | ||
| - > left, rightより、smaller, largerの方が情報が載ると思いました。 | ||
| - その通りだと思った。left, rightはポインタのleft, rightとも混ざるのが良くない。 | ||
| - その他、[実装1](#実装1)を振り返って: | ||
| - `root`がNoneのときや、部分木の根がNoneのとき、if left_root.val > right_root.valでAttributeErrorになるのが良くない。 | ||
| - ロジックが複雑で気が配れていなかった。 | ||
|
|
||
| ## Step 3 | ||
|
|
||
| ### 実装2 | ||
|
|
||
| ```python3 | ||
| from lintcode import ( | ||
| TreeNode, | ||
| ) | ||
|
|
||
| class Solution: | ||
| def split_b_s_t(self, root: Optional[TreeNode], v: int) -> Optional[TreeNode]: | ||
| """split bst by value v. return the subtree with more nodes. | ||
| please note that this method is destrutive. | ||
| """ | ||
| def split_bst_aux(node): | ||
| if node is None: | ||
| return None, None | ||
|
|
||
| if node.val <= v: | ||
| root_lower, root_higher = split_bst_aux(node.right) | ||
| node.right = root_lower | ||
| return node, root_higher | ||
| else: | ||
| root_lower, root_higher = split_bst_aux(node.left) | ||
| node.left = root_higher | ||
| return root_lower, node | ||
|
|
||
| def count_nodes(node): | ||
| if node is None: | ||
| return 0 | ||
| return 1 + count_nodes(node.left) + count_nodes(node.right) | ||
|
|
||
| root_lower, root_higher = split_bst_aux(root) | ||
| size_lower = count_tree_nodes(root_lower) | ||
| size_higher = count_tree_nodes(root_higher) | ||
|
|
||
| if size_lower > size_higher: | ||
| return root_lower | ||
| return root_higher | ||
| ``` | ||
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.
一回ですることもできますが、おそらくこういう風にツーパスにしたほうが読みやすいでしょうね。
また、ループに直すことも可能ですが、結構面倒です。