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
260 changes: 260 additions & 0 deletions easy/108/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# Step1

かかった時間:5min

計算量:nums.length=Nとして、

時間計算量:O(N)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

各レベルでO(n/2)のコピーコストが発生して、O(n log n)になりませんか?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

なんだかよく分からなくなってしまいました。。
時間がO(NlogN)で空間がO(N)でしょうか?
#26 (comment)

ちょっと考えます。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

コピーコストの部分は
#26 (comment)
と同様、

再帰 1 回目: N / 2
再帰 2 回目: N / 4
再帰 3 回目: N / 8
...
で、 N / 2 + N / 4 + N / 8 + ... <= N だと思いました。

で、 O(N) だと思いました。

Copy link
Copy Markdown

@nodchip nodchip Jul 26, 2024

Choose a reason for hiding this comment

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

上のコメント間違えました。 @kazukiii さんのおっしゃる通り、各レベル k で N / 2^k * 2^k = N のコピーコストが発生し、レベルの数が log N なので、O(N log N) だと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

纏めると、
時間計算量:0(NlogN)
空間計算量:0(N)

インデックス管理方式にすると
時間計算量:0(N)
空間計算量:0(logN)

ですかね。


空間計算量:O(NlogN)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

空間計算量 O(N log N) でしょうか…?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

考え方間違ってたらすみません。以下のように考えました。

再帰1回目:
nums[:root_value_index]nums[root_value_index + 1:]でサイズN-1のコピーが発生
再帰2回目:
同様にサイズN-2のコピーが発生
...
再帰k回目:
同様にサイズN-kのコピーが発生

k=logNなので、\sum_{i=1}^k (N-i) = kN - \frac{k(k+1)}{2} = NlogN - \frac{logN(logN+1)}{2}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

あ、減る数が違いますね。。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

毎回N-(2^k-1)のコピーが発生して、
\sum_{i=1}^k (N-(2^i-1)) = Nk + k + 2(2^k-1)

かな。。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

再帰 1 回目: N / 2
再帰 2 回目: N / 4
再帰 3 回目: N / 8
...
で、 N / 2 + N / 4 + N / 8 + ... <= N だと思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

あ、ようやく理解しました。
私はleftとrightを並列で計算してましたね。。


recursive-DFS
```python
# 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]:
if not nums:
return None

root_value_index = len(nums) // 2
root = TreeNode(val = nums[root_value_index])
root.left = self.sortedArrayToBST(nums[:root_value_index])
root.right = self.sortedArrayToBST(nums[root_value_index + 1:])

return root
```
思考ログ:
- 直近で解き方を見ている
- 配列がソートされているので真ん中で割って根から順に木を生やせば良い
- 例によって再帰なのでスタックを気にしておく
- 今回はlog(10^4)なので問題なさそう
- 配列のスライスがうまく機能しているか(漏れたりしてないか)少し脳内テスト

loop-DFS
```python
# 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]:
root = TreeNode()
node_nums_pairs = [(root, nums)]
while node_nums_pairs:
node, subset_nums = node_nums_pairs.pop()
mid_index = len(subset_nums) // 2
node.val = subset_nums[mid_index]

left_nums = subset_nums[:mid_index]
if left_nums:
node.left = TreeNode()
node_nums_pairs.append((node.left, left_nums))
right_nums = subset_nums[mid_index + 1:]
if right_nums:
node.right = TreeNode()
node_nums_pairs.append((node.right, right_nums))

return root
```
思考ログ:
- 配列を連れ回しているのは良くないか
- インデックスで管理すればいいよねという話

# Step2

講師役目線でのセルフツッコミポイント:
- 特に思いつかなかった

参考にした過去ログなど:
- https://github.com/Yoshiki-Iwasa/Arai60/pull/28
- midの取り方(left or right)に気が回っていなかった
- https://github.com/kazukiii/leetcode/pull/25
- 確かに先に木を作る方法もあった
> dummyの値を持ったcomplete binary treeを構築して、inorder traversalしながら値をセットしても良さそう
- ボックス化
- https://ja.wikipedia.org/wiki/%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E5%8C%96
- https://github.com/fhiyo/leetcode/pull/26
- https://github.com/Mike0121/LeetCode/pull/13
- https://github.com/hayashi-ay/leetcode/pull/29
- 2分木でもsentinel
- https://github.com/hayashi-ay/leetcode/pull/29/files#r1593455812
- https://github.com/sakupan102/arai60-practice/pull/25
- https://github.com/rossy0213/leetcode/pull/13
- https://discord.com/channels/1084280443945353267/1183683738635346001/1209195844511858688

recursive-DFS(インデックス処理, 右半開区間ver)
```python
# 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 build_bst(left: int, right: int):
if left >= right:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[質問]
半開区間で考えているときrightがleftより左に来ることはないので、個人的にはleft == rightと書きたくなりますが、
ここの部分どう考えているか知りたいです。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

あまり深い拘りはないのですが、どこかのコメントでこちらの書き方の方が親和性があるとあったので採用しました。

確かにleft == rightで止まる実装にはなってるんですけど、そこまで読まなくても、left > rightになる場合大丈夫かしらと不安になる必要がなくなるからでしょうか。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

なるほど、読み手の安心感という観点ですね。自分もこの書き方を使っていこうと思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

安心感というよりは、上から読んでいったときに、left > right の場合がないと確定するのは、sortedArrayToBST の定義が終わった時になりますね。しかも、数学的帰納法を利用して考えることになります。

読む人の「心の理論」
https://discord.com/channels/1084280443945353267/1225849404037009609/1234206158630289450

パズルを解かせる
https://discord.com/channels/1084280443945353267/1200089668901937312/1211248049884499988

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ありがとうございます、納得しました。
以前下の「パズルを解かせる」を読んだのですが、この感覚まだ染み付いていませんでした。。

return None

mid = (left + right) // 2
node = TreeNode(nums[mid])
node.left = build_bst(left, mid)
node.right = build_bst(mid + 1, right)
return node

return build_bst(0, len(nums))
```
思考ログ:
- 一つ関数を定義する必要があるが、インデックスで処理できるからエコな実装になる

recursive-DFS(インデックス処理, 閉区間ver)
```python
# 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 build_bst(left: int, right: int):
if left > right:
return None

mid = (left + right) // 2
node = TreeNode(nums[mid])
node.left = build_bst(left, mid - 1)
node.right = build_bst(mid + 1, right)
return node

return build_bst(0, len(nums) - 1)
```
思考ログ:
- 右半開区間の方が分かりやすいか

親と子の情報を積んでいくやり方
```python
# 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]:
left, mid, right = 0, len(nums) // 2, len(nums)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

好みかもしれませんが、3変数横並びだと少し認知負荷あるかと思いました。
midは次の行でも良い気もします。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

確かに演算も入っているし見にくかったかもしれませんね。。
ありがとうございます!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

野田さんから以下のようなコメントをもらったことがあるので、ただの代入であればそれぞれで宣言したほうがよいかもです。

thonda28/leetcode#3 (comment)

root = TreeNode(nums[mid])
# [(parent_node, index_range_for_child, is_left_child)]
parent_with_child_infos = [
(root, (left, mid), True),
(root, (mid + 1, right), False)
]
while parent_with_child_infos:
parent_node, (left, right), is_left = parent_with_child_infos.pop()
if left >= right:
continue
mid = (left + right) // 2
child_node = TreeNode(nums[mid])
if is_left:
parent_node.left = child_node
else:
parent_node.right = child_node
parent_with_child_infos.append((child_node, (left, mid), True))
parent_with_child_infos.append((child_node, (mid + 1, right), False))

return root
```
思考ログ:
- 積む情報が多い、スタックの問題もあるが、再帰は簡潔に書けて良い

先に木を作ってin-orderで値を埋めていく方法
```python
from collections import deque


# 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]:
# complete binary tree
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

コメントで関数名の補足わかりやすいです。

def build_cbt(index: int):
if index >= len(nums):
return None
root = TreeNode()
root.left = build_cbt(2 * index + 1)
root.right = build_cbt(2 * index + 2)
return root

nums_queue = deque(nums)
def set_values(cbt_root: Optional[TreeNode]) -> None:
nodes = [cbt_root]
while nodes:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これnodesとwhileいらなくないでしょうか?
nodesにcbt_root以外入らないような気がしてます

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ご明察です。

書き直しました。

# 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]:
        # complete binary tree
        def build_cbt(index: int):
            if index >= len(nums):
                return None
            root = TreeNode()
            root.left = build_cbt(2 * index + 1)
            root.right = build_cbt(2 * index + 2)
            return root
        
        nums_queue = deque(nums)
        def set_values(cbt_root: Optional[TreeNode]) -> None:
            if not cbt_root:
                return None
            set_values(cbt_root.left)
            cbt_root.val = nums_queue.popleft()
            set_values(cbt_root.right)

        cbt_root = build_cbt(0)
        set_values(cbt_root)

        return cbt_root

node = nodes.pop()
if not node:
continue
set_values(node.left)
node.val = nums_queue.popleft()
set_values(node.right)

cbt_root = build_cbt(0)
set_values(cbt_root)

return cbt_root
```
思考ログ:
- in-orderを書く機会が少ないのでせっかくなので書いておく
- https://github.com/kazukiii/leetcode/pull/25/files
- nonlocalを使わない形に少しアレンジ

# Step3

かかった時間:3min

```python
# 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]:
if not nums:
return None

root = TreeNode()
node_range_pairs = [(root, (0, len(nums)))]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

好みですが、ちょっと同じ行にカッコが多いなと思いました。leftとrightはまとめなくても良いかもです。

while node_range_pairs:
node, (left, right) = node_range_pairs.pop()
mid = (left + right) // 2
node.val = nums[mid]

if left < mid:
node.left = TreeNode()
node_range_pairs.append((node.left, (left, mid)))
if mid + 1 < right:
node.right = TreeNode()
node_range_pairs.append((node.right, (mid + 1, right)))

return root
```
思考ログ:
- 再帰でいい気がするが、スタック版を練習も兼ねて

# Step4

```python
```
思考ログ: