-
Notifications
You must be signed in to change notification settings - Fork 0
108._Convert_Sorted_Array_to_Binary_Search_Tree #3
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,139 @@ | ||
| # 108. Convert Sorted Array to Binary Search Tree <!-- omit in toc --> | ||
|
|
||
| ## 1. 問題 | ||
|
|
||
| ### 1.1. リンク | ||
|
|
||
| <https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/> | ||
|
|
||
| ### 1.2. 問題概要 (閲覧制限のある問題の場合のみ) | ||
|
|
||
| ## 2. 次に取り組む問題のリンク | ||
|
|
||
| <https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/> | ||
|
|
||
| ## 3. ステップ1 | ||
|
|
||
| ### 3.1. コード | ||
|
|
||
| 再帰処理ならこんな感じ;左右の部分木については再帰してお任せ.自分は計算してもらった部分木の根を左右の子供に据えて終わり. | ||
|
|
||
| ```python | ||
| def build_binary_search_tree(array:List[int], begin:int, end:int) -> Optional[TreeNode]: | ||
| if begin >= end: | ||
| return None | ||
| mid = (begin + end) // 2 | ||
| left = build_binary_search_tree(array, begin, mid) | ||
| right = build_binary_search_tree(array, mid+1, end) | ||
| return TreeNode(val=array[mid], left=left, right=right) | ||
| ``` | ||
|
|
||
| 反復処理. | ||
| (部分木の根, 対応する部分配列の開始インデックス,終了インデックス) の組を順に処理することだけを考えていたらそれ以外が不自然になった; | ||
|
|
||
| - 再帰処理では左右の子供がNoneかどうかをチェックする必要がなかったのに,この実装だと部分木の根を先に作成する都合上, | ||
| - 変数のシャドーイングに無頓着 (`begin`, `end`). | ||
| - 再帰処理を思い浮かべた後なのに手癖でBFSしてる. | ||
| - 実装が再帰処理に比べてやや強引. | ||
|
|
||
| ```python | ||
| from collections import deque | ||
|
|
||
|
|
||
| class Solution: | ||
| def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]: | ||
| if len(nums) == 0: | ||
| return None | ||
| begin = 0 | ||
| end = len(nums) | ||
| root = TreeNode(val=nums[( begin + end ) // 2]) | ||
| dq = deque([(root, begin, end)]) | ||
|
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. 変数名に deque を表す略語 dq と付けても、あまり情報がないように感じました。中にどのような要素が含まれるかを表す端的な英単語・英語句を付けることをお勧めいたします。 |
||
| while dq: | ||
| sub_root, begin, end = dq.popleft() | ||
| mid = (begin + end) // 2 | ||
| if begin < mid: | ||
| sub_mid = (begin + mid) // 2 | ||
| sub_root.left = TreeNode(val=nums[sub_mid]) | ||
| dq.append((sub_root.left, begin, mid)) | ||
| if mid + 1 < end: | ||
| sub_mid = (mid + 1 + end) // 2 | ||
| sub_root.right = TreeNode(val=nums[sub_mid]) | ||
| dq.append((sub_root.right, mid + 1, end)) | ||
| return root | ||
|
|
||
| ``` | ||
|
|
||
| ### 3.2. 時間・空間計算量 | ||
|
|
||
| `nums` のサイズを$N$ とする. | ||
| `nums`のすべての要素をそれぞれちょうど1回だけ部分木の根として扱うので,$N$回は確定. | ||
| さらに,二分探索木の葉の子(Noneになるやつら)に対応する部分配列をdequeに入れる回数は高々二分探索木の葉の2倍で,二分探索木の葉は高々$\lfloor \frac{N+1}{2} \rfloor$なので,全体の反復回数も$O(N)$. | ||
| よって,時間計算量$O(N)$. | ||
|
|
||
| 空間計算量も,以下の2点により$O(N)$; | ||
|
|
||
| - 引数の配列 ... $N$個の要素を持つ. | ||
| - 新たに作るノード ... ちょうど $N$ 個. | ||
| - deque が持ちうる最大の要素数 ... 二分探索木の葉の個数($:=m$)の二倍と一緒なので,$2m\ge 2\lfloor \frac{N+1}{2}\rfloor = N+1$ より$O(N)$個. | ||
| - BFSじゃなくてDFSにすればここは$O(\log N)$になる.(二分探索木の深さを$d$として高々$d+2$個しかスタックに積まないので) | ||
|
|
||
| ## 4. ステップ2 | ||
|
|
||
| ### 4.1. コード | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]: | ||
| if len(nums) == 0: | ||
| return None | ||
| root_idx = len(nums) // 2 | ||
| root = TreeNode(val=nums[root_idx]) | ||
| stack = [ | ||
| (root, False, root_idx + 1, len(nums)), | ||
| (root, True, 0, root_idx), | ||
| ] | ||
| while stack: | ||
| parent, is_left, begin, end = stack.pop() | ||
| if begin >= end: | ||
| continue | ||
| mid = (begin + end) // 2 | ||
| child = TreeNode(val=nums[mid]) | ||
| if is_left: | ||
| parent.left = child | ||
| else: | ||
| parent.right = child | ||
| stack.append((child, False, mid + 1, end)) | ||
| stack.append((child, True, begin, mid)) | ||
| return root | ||
|
|
||
| ``` | ||
|
|
||
| ### 4.2. 講師陣のコメントとして想定されること | ||
|
|
||
| ### 4.3. 他の人のコードを読んで考えたこと | ||
|
|
||
| - [りょう](https://github.com/ryoooooory/LeetCode/pull/27/changes) さん | ||
| - タブサイズ2のときは4の時に比べてブロックの識別性がやや下がる気がする. | ||
|
|
||
| - [h1rosaka](https://github.com/h1rosaka/arai60/pull/27/changes#diff-7e1a92af8dc65ffab400b9bf2416693d15f370a5cfb6ac37e009f85a67c03a32) さん | ||
| - `is_left` に相当する変数として文字列の `direction` を使っていた,定数に置くかしたいなぁ → コメントでは「Enum使いましょう」,その通りだ | ||
| - BFSのためのスタック変数名 `stack` にコメントが入っていた,僕はBFSするんだね~とwhile文冒頭でわかる点において,まだ技術ドリブン命名のなかではセーフよりだと考えている.が, `stack` と書いてあとでdfsに直した時にそのままの変数名にして罠作る可能性も0じゃない. | ||
|
|
||
| ### 4.4. 改善するときに考えたこと | ||
|
|
||
| - 変数のシャドーイングをなくす(`begin`, `mid`, `end` は while ループに取っておく.) | ||
|
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. これはシャドーイングではないように思いました。 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は比較的再代入にも寛容な気がします(代入の度にデータ型が変わる、みたいなのは止めたほうがいいでしょうが…)。本PRのコードも、利用範囲や流れの上でも、割と追いやすく問題とは思いませんでした。 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. コメントありがとうございます! |
||
| - while より前の `mid` → `mid`というよりかは根の要素をどこに取ってくるか?なのでそういう名前にする. | ||
| - while より前の `begin`, `end`をどうするか? | ||
| - そのままにして,while ループで `b`, `m`, `e` など略称にする→たまに見るけどあんまりしっくり来ない. | ||
| - そのままにして,while ループで `sub_` の prefix をつける("部分木の"の意)→変数名が長い割には意味的にはわかりやすくならないしなぁ. | ||
| - そのままにして,木全体も部分木の一種としてみなせるのでシャドーイング自体をも許す→今回の場合 `root` は返り値に使うためにシャドーイングしてはならないのがちょっと嫌な気持ちになる. | ||
| - ハードコードに変更する(0, `len(nums)` )→まぁその数行下に意味がわかる変数名 `begin`, `end` がいるから許容? | ||
| - うーん,コーディングテスト本番にこんなところまで考えられない気がする.動くコード書いたあと「業務で書くとしたらどんなこと思う?」ときかれたら上の話をするくらい? | ||
|
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. 私はビッグテックでの面接官経験はありませんが、コーディングテストは業務を想定したものだと思います(業務で、手を動かす時間がない面接官が、受験者に対して、とある機能の実装をお願いしようとする想定)。コーディングテスト中に気を使えないところは業務でも気を使えないのだと思われるのではないでしょうか。したがって
というのは聞かれる可能性が低いと予想します。 |
||
| - 再帰がDFSするときのコールスタックの挙動に近い形で実装する. | ||
| - 再帰のときの引数に乗っていないコンテキストである左右どちらの子か?を乗せれば楽に再現できる | ||
| - 関数呼び出し時にコールスタックにプログラムカウンタ乗せることと対応する | ||
| - 部分木の根は先に作る(ここだけ再帰処理と挙動が異なるが,些末). | ||
|
|
||
| ## 5. ステップ3 | ||
|
|
||
| ステップ2と同じ. | ||
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.
() のすぐ内側にスペースを空けるのは、あまり見ないように思いました。
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.
英文書くときのクセで空白連発してしまうのと,日常のコーディングではフォーマッタにまかせている部分なので,やりがちです...
自分で見直したときにスルーしてしまうのは,やはりコードリーディングが足りないシグナルなんでしょうか?
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.
まあ、別に構わないとは思いますが、ルールが決まっている環境でレビューを受けて1-2週間くらい活動すると固定されます。