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
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
## Step 1

- 問題文
- 二分木の根`root`を与えられ、ノードの値を"ジグザグ"レベル順に走査して返す問題。(i.e., from left to right, then right to left for the next level and alternate between).
- 制約:
- The number of nodes in the tree is in the range [0, 2000].
- -100 <= Node.val <= 100

### 実装1

- アルゴリズムの選択
- BFS/DFS。素直にBFSで。
- [前回の問題](https://leetcode.com/problems/binary-tree-level-order-traversal/)とほとんど同じように書ける。
- 二重ループを使う。
- levelごとのvaluesは、level内では常にleft to rightで一旦貯めていき、複数レベルのリストに格納する直前に反転するかどうか決めたらいい。
- 実装
- level変数を外側のループが回るごとのインクリメントする。
- 1-indexed(rootのlevelが1)の方がわかりやすいと思い、そうする。
- if level % 2 == 0: 反転、とする。
- 計算量
- Time: O(n)
- Space: O(n)

```python3
from typing import Optional, List


# 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 zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []

values_ordered_by_level = []
level_nodes = [root]
level = 1 # root level is 1
while level_nodes:
level_values = []
next_nodes = []

for node in level_nodes:
level_values.append(node.val)
if node.left is not None:
next_nodes.append(node.left)
if node.right is not None:
next_nodes.append(node.right)

if level % 2 == 0:
# zigzag level
level_values.reverse()

values_ordered_by_level.append(level_values)
level_nodes = next_nodes
level += 1

return values_ordered_by_level
```

ここまで8分。

### 実装2

- DFSでも書いてみる。
- こちらもzigzagにするのは後処理的にやるのがシンプルに思えた。

```python3
from typing import Optional, List


class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []

values_ordered_by_level = []
nodes_with_level = [(root, 1)]
while nodes_with_level:
node, level = nodes_with_level.pop()

while level > len(values_ordered_by_level):
values_ordered_by_level.append([])
values_ordered_by_level[level - 1].append(node.val)

# LIFO: popped from left to right
if node.right is not None:
nodes_with_level.append((node.right, level + 1))
if node.left is not None:
nodes_with_level.append((node.left, level + 1))

for level in range(1, len(values_ordered_by_level) + 1):
if level % 2 == 0:
values_ordered_by_level[level - 1].reverse()

return values_ordered_by_level
```

## Step 2

- レビュー by GPT-5
- > 反転自体は問題ありませんが、反転を避けたい場合は「収集中に `appendleft` を使う」手があります。具体的には偶数レベルでは `appendleft(node.val)`、奇数レベルでは `append(node.val)` として `deque` に詰め、最後に `list(...)` 化します。これは「コピー+反転」を避けられます。
Copy link

Choose a reason for hiding this comment

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

list にしているので結果的にコピーされていますよね……。
私は直感的にはこちらのほうが遅い気がします。(どうせそんなに問題になるレベルではないですが。)

- これは良さそう。O(n)の定数倍の最適化になる。
- ところで、sequence.reverse()について調べていたところ、以下のように書いてあり興味深かったです。
- > Reverse the items of sequence in place. This method maintains economy of space when reversing a large sequence. **To remind users that it operates by side-effect, it returns None.**
- https://docs.python.org/ja/3.13/library/stdtypes.html#sequence.reverse
Copy link

Choose a reason for hiding this comment

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

sequence.reverse() の他に、built-in の reversed というのもありますね。
https://docs.python.org/3/library/functions.html#reversed

- https://github.com/nanae772/leetcode-arai60/pull/27
- > levelの偶奇で判断するのは要件から離れていると感じるので、levelが上がるにつれてis_left_to_rightのbool値を反転させていく方が好みです。
- 確かに。実装するときも、剰余が0 or 1で迷ったので読み手からしてもわかりくい(=パズルになっている)だろう。
- 情報量としてはフラグで十分なので、levelの偶奇で判断する実装するということは、levelによる分岐の拡張性を残したいという意志である。
- https://github.com/garunitule/coding_practice/pull/27
- > 当時はどちらかというとreverseするための計算量O(N)を節約できるからdequeよさそう、と思っていたのですが、今改めて考えると、おっしゃる通り結局listに変換する際にコピーをするのでその分結局O(N)発生してしまっていて、あまりdequeにすることの旨みはなかったかもしれません。。
- よく考えてみると、reverseの有無によらずlist()でO(N)の計算が入るので、逆に遅くなるまでありそう。
- 例えばリストで頑張るなら、最初にそのレベルの全ノードのプレイスホルダーを作っておいて、zigzagする場合は後ろからインデックスアクセスで詰めていけば若干速いかもしれない。
Copy link

Choose a reason for hiding this comment

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

Python の高速化はあまりというところがあります。
Python を使っている時点で C などから50-100倍くらい遅いので、ネイティブコードで書かれた関数をどう呼ぶかという勝負になってきます。numpy など。
本当に速度が必要だと C を使って拡張モジュールを作ることもできます。
https://docs.python.org/3/extending/extending.html

- 可読性が下がるが、まぁ許容できる範疇と考えた。(不要なパズルは生じないと思う。)

## Step 3

### 実装3

[実装1](#実装1)を改善する。

```python3
from typing import Optional, List


class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []

level_order_values = []
level_nodes = [root]
is_left_to_right = True
while level_nodes:
level_values = [0] * len(level_nodes)
next_nodes = []

for i, node in enumerate(level_nodes):
if is_left_to_right:
index = i
else:
index = len(level_nodes) - 1 - i
Copy link

Choose a reason for hiding this comment

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

Python はマイナスで後ろからアクセスしてくれるので、-1 - i でいいですかね?

level_values[index] = node.val
if node.left is not None:
next_nodes.append(node.left)
if node.right is not None:
next_nodes.append(node.right)

level_order_values.append(level_values)
level_nodes = next_nodes
is_left_to_right = not is_left_to_right

return level_order_values
```