Skip to content
Open
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
158 changes: 158 additions & 0 deletions 108_convert-sorted-array-to-binary-search-tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# 108. Convert Sorted Array to Binary Search Tree

## 1st

### ①

再帰の深さがlogオーダー (定数倍も問題ない) なので問題ないと思い再帰で実装した。

所要時間: 5:33

n: len(nums)
- 時間計算量: O(n)
- 空間計算量: O(log(n))

```py
# 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 sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
def to_bst(begin: int, end: int) -> Optional[TreeNode]:
if begin == end:

Choose a reason for hiding this comment

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

個人的には、二分探索の終了条件は不等号もつけた方が分かりやすいと思います。
趣味の範囲かもしれません。

return None
mid = (begin + end) // 2

Choose a reason for hiding this comment

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

単語は略さない方が良いと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

何となくmidは市民権を得ている感覚があったのですが、微妙ですかね。
わりとしっくり来ているのでどうしようかな...もうちょっと試して決めてみようかと思います

Copy link

Choose a reason for hiding this comment

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

mid という形容詞はありますね。
https://www.merriam-webster.com/dictionary/mid

node = TreeNode(nums[mid])
node.left = to_bst(begin, mid)
node.right = to_bst(mid + 1, end)
return node

return to_bst(0, len(nums))
```

### ②

スタックに直した版。node_and_ranges_stackにappendする値を間違えており (right側で(node.right, mid, end)を積むようにしていた)、図を書くまで何が問題か分からず時間がかかった。
こういう勘違いはこのアルゴリズムで何をしているかきちんと分かっており言語化できればあまり起こらないと思うので、理解不足なのだろう。

node_and_ranges_stackはもう少しいい名前があるかも。Pythonでは問題ないがendはちょっとキーワード感あるかもしれない?

Choose a reason for hiding this comment

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

冗長に感じる場合、_stackは省略しても良いのかなと思いました。(好みの問題かもです。)


所要時間: 16:50

n: len(nums)
- 時間計算量: O(n)
- 空間計算量: O(log(n))

```py
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
if not nums:
return None
root = TreeNode()
node_and_ranges_stack = [(root, 0, len(nums))]
while node_and_ranges_stack:
node, begin, end = node_and_ranges_stack.pop()
mid = (begin + end) // 2
node.val = nums[mid]
if begin < mid:
node.left = TreeNode()
node_and_ranges_stack.append((node.left, begin, mid))
if mid + 1 < end:
node.right = TreeNode()
node_and_ranges_stack.append((node.right, mid + 1, end))
return root
```

## 2nd

### 参考

- https://discord.com/channels/1084280443945353267/1196472827457589338/1238907849422274622
- https://discord.com/channels/1084280443945353267/1200089668901937312/1237995792371945573
- https://discord.com/channels/1084280443945353267/1227073733844406343/1237649500630421527
- https://discord.com/channels/1084280443945353267/1201211204547383386/1217525976691507221

parent, left, rightをstackから取るならこんな感じになるだろうか。
平衡二分探索木を作っていることがちょっと分かりづらいかもしれない。
parent_with_range_stackはいい名前が思いつかない。

```py
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
if not nums:
return None
sentinel = TreeNode()
parent_with_range_stack = [(sentinel, 0, len(nums), True)]
Copy link

@sakupan102 sakupan102 Jun 25, 2024

Choose a reason for hiding this comment

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

最初にsentinelの左側にnodeをくっつけるのがほんの少しだけ気になりました。
これやるなら最初の処理をwhileの外で行うとかですかね。

while parent_with_range_stack:
parent, begin, end, is_left = parent_with_range_stack.pop()
if begin == end:
continue
mid = (begin + end) // 2
node = TreeNode(nums[mid])
if is_left:
parent.left = node
else:
parent.right = node
parent_with_range_stack.append((node, begin, mid, True))
parent_with_range_stack.append((node, mid + 1, end, False))
return sentinel.left
```


行き帰りで異なる処理をするようにstackで実装した版。
これをleft, rightへの参照を行きのときに作ってstackに積むようにすれば②のようにできる。

```py
from dataclasses import dataclass

class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
root_ref = Box(None)
stack = [('go', root_ref, Box(None), Box(None), 0, len(nums))]
while stack:
go_back, node_ref, left_node_ref, right_node_ref, begin, end = stack.pop()

Choose a reason for hiding this comment

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

自分だけかもしれませんが、refが何を参照しているかが少し戸惑ったのですが、Boxを利用される場合はよくある書き方でしょうか?node, left_child, right_childとかでも良い気がしました。見当違いでしたらすみません。

Copy link
Owner Author

Choose a reason for hiding this comment

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

よくあるのかは分からないんですが、個人的にはPythonのような動的型付け言語で _ref の接尾辞を書かずnodeみたいに書くと、TreeNodeなのかTreeNodeを指すコンテナなのかが分からず書きにくくなる印象がありました。

if go_back == 'go':
if begin == end:
continue
mid = (begin + end) // 2
node_ref.value = TreeNode(nums[mid])
stack.append(('back', node_ref, left_node_ref, right_node_ref, begin, end))
stack.append(('go', left_node_ref, Box(None), Box(None), begin, mid))
stack.append(('go', right_node_ref, Box(None), Box(None), mid + 1, end))
node_ref.value.left = left_node_ref.value

Choose a reason for hiding this comment

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

ここの処理が行きがけでも行われるのが少し違和感があります。
個人的には
行き→下にTreeNodeを作るようにたのむ
帰り→下で作ったTreeNodeを繋ぎ合わせる
この方法のほうが再帰との対応が良いかと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

すみません、あまりイメージが湧かず...質問させてください。

ここの処理が行きがけでも行われるのが少し違和感があります。

これはどこの部分になるでしょうか?

行き→下にTreeNodeを作るようにたのむ
帰り→下で作ったTreeNodeを繋ぎ合わせる

もし良ければでいいんですが、コードがどんな感じになるか書いていただけないでしょうか?

Choose a reason for hiding this comment

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

イメージこんな感じです
まあelseを足しただけですが...

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
        root_ref = Box(None)
        stack = [('go', root_ref, Box(None), Box(None), 0, len(nums))]
        while stack:
            go_back, node_ref, left_node_ref, right_node_ref, begin, end = stack.pop()
            if go_back == 'go':
                if begin == end:
                    continue
                mid = (begin + end) // 2
                node_ref.value = TreeNode(nums[mid])
                stack.append(('back', node_ref, left_node_ref, right_node_ref, begin, end))
                stack.append(('go', left_node_ref, Box(None), Box(None), begin, mid))
                stack.append(('go', right_node_ref, Box(None), Box(None), mid + 1, end))
            else: #go_back == 'back'
                node_ref.value.left = left_node_ref.value
                node_ref.value.right = right_node_ref.value
        return root_ref.value

このelseがないとgo_back === 'go'のときも左右のノードをつなげる処理が行われていて、行きと帰りで違う処理をしている理由があまりないかなと思いました。

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 go_back == 'go' のブロックの最後にcontinue付けてるつもりでいました。
たしかにおっしゃる通り今のコードだとgoの処理でも左右につなげてますね、失礼しました 🙇

node_ref.value.right = right_node_ref.value
return root_ref.value


@dataclass
class Box:
value: Optional[TreeNode]
```


## 3rd

この問題を解けと言われたら再帰で実装するだろうが、今回は時間がかかったスタックの方を練習した。

```py
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
if not nums:
return None
root = TreeNode()
node_with_range_stack = [(root, 0, len(nums))]
while node_with_range_stack:
node, begin, end = node_with_range_stack.pop()
mid = (begin + end) // 2
node.val = nums[mid]
if begin < mid:
node.left = TreeNode()
node_with_range_stack.append((node.left, begin, mid))
if mid + 1 < end:
node.right = TreeNode()
node_with_range_stack.append((node.right, mid + 1, end))
return root
```