Skip to content

Conversation

@Kaichi-Irie
Copy link
Owner

問題へのリンク

103. Binary Tree Zigzag Level Order Traversal

言語

Python

問題の概要

自分の解法

step1

class Solution:
    def zigzagLevelOrder(self, root) -> list[list[int]]:
        if not root:
            return []
        LEFT_TO_RIGHT = 1
        RIGHT_TO_LEFT = -1
        direction = LEFT_TO_RIGHT
        nodes = [root]
        zigzag_level_nodes = []
        while nodes:
            node_vals = []
            next_level_nodes = []
            while nodes:
                node = nodes.pop()
                node_vals.append(node.val)
                if direction == LEFT_TO_RIGHT:
                    if node.left:
                        next_level_nodes.append(node.left)
                    if node.right:
                        next_level_nodes.append(node.right)
                else:
                    if node.right:
                        next_level_nodes.append(node.right)
                    if node.left:
                        next_level_nodes.append(node.left)

            zigzag_level_nodes.append(node_vals)
            nodes = next_level_nodes
            direction *= -1
        return zigzag_level_nodes
  • 時間計算量:O(n)
  • 空間計算量:O(n)

step2

from collections import deque


class Solution:
    def zigzagLevelOrder(self, root: TreeNode | None) -> list[list[int]]:
        if not root:
            return []
        zigzag_level_nodes = []
        nodes = deque([(root, 0)])
        while nodes:
            next_nodes = deque([])
            while nodes:
                node, level = nodes.popleft()
                while len(zigzag_level_nodes) <= level:
                    zigzag_level_nodes.append(deque([]))

                # left to right
                if level % 2 == 0:
                    zigzag_level_nodes[level].append(node.val)
                # right to left
                else:
                    zigzag_level_nodes[level].appendleft(node.val)

                if node.left:
                    next_nodes.append((node.left, level + 1))
                if node.right:
                    next_nodes.append((node.right, level + 1))

            nodes = next_nodes

        return [list(deq) for deq in zigzag_level_nodes]
  • BFSで各レベルごとに走査していくのはstep1と同じ。だが、走査するのは左から順に行い、値の追加だけzigzagになるようにする。

step3

レベルごとに走査を行う方法(step3_1.py)。直感的だが、ネストが深くなる。
リストを再代入していくのもあまり良くないかも。

from collections import deque


class Solution:
    def zigzagLevelOrder(self, root: TreeNode | None) -> list[list[int]]:
        zigzag_node_vals: list[deque[int]] = [deque([])]  # deque
        nodes = [root]
        next_level_nodes = []
        level = 0

        while nodes:
            for node in nodes:
                if node is None:
                    continue
                next_level_nodes.append(node.left)
                next_level_nodes.append(node.right)
                # left to right; FIFO
                if level % 2 == 0:
                    zigzag_node_vals[level].append(node.val)
                else:  # right to left; LIFO
                    zigzag_node_vals[level].appendleft(node.val)
            nodes = next_level_nodes
            next_level_nodes = []
            level += 1
            zigzag_node_vals.append(deque([]))
        return [list(deq) for deq in zigzag_node_vals]
  • 点を走査する順番自体を工夫するのか、あるいは値の追加の仕方を工夫するのか。
    • 今回なら、点は普通に左から順に走査していき、値の追加の仕方をzigzagにするのが楽
  • 初期値の設定、各ループでの更新のタイミングは統一するのが良い
    • 例えば、next_level_nodesの初期化をループの外で行い、ループの最後で空にするようにした
    • levelの初期化をループの外で行い、ループの最後でインクリメントするようにした

nodesをキューで管理して、ネストを1つ浅くする方法(step3_2.py

from collections import deque


class Solution:
    def zigzagLevelOrder(self, root: TreeNode | None) -> list[list[int]]:
        zigzag_node_vals: list[deque[int]] = []  # deque
        nodes_queue = deque([(root, 0)])
        while nodes_queue:
            node, level = nodes_queue.popleft()
            if node is None:
                continue
            while len(zigzag_node_vals) <= level:
                zigzag_node_vals.append(deque([]))
            if level % 2 == 0:  # from left to right; FIFO
                zigzag_node_vals[level].append(node.val)
            else:  # from right to left; LIFO
                zigzag_node_vals[level].appendleft(node.val)

            nodes_queue.append((node.left, level + 1))
            nodes_queue.append((node.right, level + 1))
        return [list(deq) for deq in zigzag_node_vals]

reverseを使う方法(step3_3.py

class Solution:
    def zigzagLevelOrder(self, root: TreeNode | None) -> list[list[int]]:
        zigzag_level_order_vals: list[list[int]] = []  # deque
        nodes = [root]
        next_level_nodes = []
        level = 0

        while True:
            zigzag_vals = []
            for node in nodes:
                if node is None:
                    continue
                zigzag_vals.append(node.val)
                next_level_nodes.append(node.left)
                next_level_nodes.append(node.right)

            if not next_level_nodes:
                return zigzag_level_order_vals
            # align node vals from right to left
            if level % 2 == 1:
                zigzag_vals.reverse()
            zigzag_level_order_vals.append(zigzag_vals)
            nodes = next_level_nodes
            next_level_nodes = []
            level += 1
  • dequeを使わずに、普通のリストで値を保持し、reverseで並び替える方法
  • levelごとにループを回すので、ネストは深くなるが、どのコードよりもzigzagの処理ががシンプルになる

step4 (FB)

別解・模範解答

DFS

from collections import deque


class Solution:
    def zigzagLevelOrder(self, root: TreeNode | None) -> list[list[int]]:
        if not root:
            return []
        nodes_by_level = []

        def traverse(node: TreeNode, level: int):
            while len(nodes_by_level) <= level:
                nodes_by_level.append(deque([]))
            if level % 2 == 0:
                nodes_by_level[level].append(node.val)
            else:
                nodes_by_level[level].appendleft(node.val)

            if node.left:
                traverse(node.left, level + 1)
            if node.right:
                traverse(node.right, level + 1)

        traverse(root, 0)

        return [list(d) for d in nodes_by_level]
  • 再帰関数を用いてグラフを走査していく。

  • 各レベルごとのノードのリストをdequeとして保持して、

    • level が奇数のときは走査した順、つまりFIFOの順にノードを追加していく。
    • level が偶数のときは逆順、つまりLIFOの順にノードを追加していく。
  • 最後にdequeをリストに変換して返す。

  • 時間計算量:O(n)

  • 空間計算量:O(n)


class Solution:
def zigzagLevelOrder(self, root: TreeNode | None) -> list[list[int]]:
zigzag_level_order_vals: list[list[int]] = [] # deque
Copy link

Choose a reason for hiding this comment

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

dequeコメントはミスでしょうか

Copy link
Owner Author

Choose a reason for hiding this comment

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

レビューありがとうございます。
そうです。ご指摘ありがとうございます。

zigzag_vals.reverse()
zigzag_level_order_vals.append(zigzag_vals)
nodes = next_level_nodes
next_level_nodes = []
Copy link

Choose a reason for hiding this comment

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

これはループの最初に持ってきた方が読みやすいと思います。そうするとL20も消すことができます。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにそうですね。zigzag_valsともそろった方が読みやすいと感じました。

next_level_nodes.append(node.right)

if not next_level_nodes:
return zigzag_level_order_vals
Copy link

Choose a reason for hiding this comment

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

個人的には、これはnext_level_nodes.appendの段階でnode.leftやnode.rightがNoneかどうかをチェックする処理を入れるなどして、ループの最後に持っていきたいです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

nodeを取り出したときにNoneを判定するようにすると、

  • if root is None:...
  • if node.left is not None:...
  • if node.right is not None:...
    という3つのif文が1つになるので、より簡潔だなと感じてこのようにしました。

以下のようにすれば、最後にreturnできますね。

from collections import deque


class Solution:
    def zigzagLevelOrder(self, root: TreeNode | None) -> list[list[int]]:
        zigzag_level_order_vals: list[list[int]] = []
        nodes = [root]
        level = 0
        while nodes:
            next_level_nodes = []
            zigzag_vals = deque([])
            for node in nodes:
                if node is None:
                    continue
                # from left to right
                if level % 2 == 0:
                    zigzag_vals.append(node.val)
                else:
                    zigzag_vals.appendleft(node.val)
                next_level_nodes.append(node.left)
                next_level_nodes.append(node.right)

            nodes = next_level_nodes
            if zigzag_vals:
                zigzag_level_order_vals.append(list(zigzag_vals))
            level += 1
        return zigzag_level_order_vals

Copy link

Choose a reason for hiding this comment

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

あ、そちらの方が良さそうですね!

node, level = nodes_queue.popleft()
if node is None:
continue
while len(zigzag_node_vals) <= level:
Copy link

Choose a reason for hiding this comment

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

好みかもしれませんが、個人的にはwhileではなくifの方が分かりやすい気がします。

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だと「条件が満たされれば1つ要素を追加する」という意味になりますが、while だと「条件が満たされるまで要素を追加する」という意味になってより適切だと感じました。
(もちろん、こちらのコードではどちらでも動作しますが)

continue
while len(zigzag_node_vals) <= level:
zigzag_node_vals.append(deque([]))
if level % 2 == 0: # from left to right; FIFO
Copy link

Choose a reason for hiding this comment

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

FIFOはFirst In First Outの略で、今回の問題においてはOutの順番が重要であるわけではなく、単に左から追加する、右から追加するというのが重要になるだけなので、ここのコメントのFIFOとL31のLIFOはない方が読みやすいと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにpopするわけではなく、リストをそのまま返すので、その方が適切ですね。

@tokuhirat
Copy link

すでにコメントがついている点以外は問題ないと思いました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants