LeetCode专项练习之树上宽度优先搜索(Tree Breadth-First Search)笔记

本文是根据穷码农的LeetCode刷题建议而进行专项练习时记录的心得。

最近发现自己练习时用的方法不太正确,老是想着自己先完全原创一个方法,实在写不动了才去看解析。这样做虽然有一定效果,但却非常耗时。其中,有一道中等题我前前后后花了6个小时才做完,结果发现自己之前做着做着就开始抠起了细节,完全没去用算法思想来解题,实在是得不偿失。以后,我不会再在一道题上死磕这么久了,在一个番茄钟的时间结束后如果没有想法或者思路不清晰时,我就直接查看解析。然后,根据解析重新背着写完这道题,并总结它的规律,及时应用于下一道题的训练。

今天的笔记包含基于树的宽度优先搜索(Tree Breadth-First Search)类型下的5个题目,它们在leetcode上的编号和题名分别是:

  • 102 - Binary Tree Level Order Traversal
  • 107 - Binary Tree Level Order Traversal II
  • 103 - Binary Tree Zigzag Level Order Traversal
  • 199 - Binary Tree Right Side View
  • 111 - Minimum Depth of Binary Tree

下面将根据以上顺序分别记录代码和对应心得,使用的编译器为Pycharm (Python3)。


Binary Tree Level Order Traversal

Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).

For example:
Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
return its level order traversal as:

[
  [3],
  [9,20],
  [15,7]
]

方法一:利用队列循环遍历。通过Python中的普通列表模仿队列,将每层元素推进去,并统一取出来以一个列表的形式推入需要返回的列表中。最后再看统一取出来的节点是否有左右子节点,若有则将这些子节点推入"队列"中去周而复始,直到没有子节点为止。

class Solution:
    def levelOrder(self, root: TreeNode) ->list:
        
        ans = []
        if root is None:
            return ans

        stack = []
        temp = []  # used to fill the elements in each level
        stack.append(root)

        while len(stack) != 0:
            # get the element from the level of stack
            while len(stack) != 0:
                temp.append(stack.pop(0))

            # record the values of each element from the same level
            values = []

            while len(temp) > 0:
                # put the element into answer list
                values.append(temp[0].val)

                # check if each element has children
                if temp[0].left is not None:
                    stack.append(temp[0].left)
                if temp[0].right is not None:
                    stack.append(temp[0].right)
                temp.pop(0)

            ans.append(values)

        return ans

方法二:方法一的优化。方法一虽然借助了队列思想,但在具体处理的时候反复计算列表长度,并且不断使用pop(0)来剔除列表第一个元素并左移其他元素,时间消耗过大。
因此,方法二中开辟了新的临时变量,代表下一层元素的信息。利用for循环遍历完上一层信息后,直接用下一层的值替换上一层,不再需要将本层的节点抛出后再装入下一层的节点。

class Solution:
    def levelOrder(self, root: TreeNode) ->list:
       # special considerations
        if root is None:
            return []

        # parameters
        ans = []
        cur_level = [root]  # define the elements in each level

        while cur_level:
            next_level = []  # define next level
            values = []  # define the values of each element in the same level

            # essential: use 'for' loop to get each node info
            for node in cur_level:
                values.append(node.val)

                if node.left is not None:
                    next_level.append(node.left)
                if node.right is not None:
                    next_level.append(node.right)

            ans.append(values)
            cur_level = next_level

        return ans

Binary Tree Level Order Traversal II

Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left to right, level by level from leaf to root).

For example:
Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
return its bottom-up level order traversal as:

[
  [15,7],
  [9,20],
  [3]
]

此题采用BFS时,首先应该考虑的就是遍历树,通过构建一个队列去承载树每层的节点,再根据队列提取我们需要的节点信息。因此,此题解法和上一题完全相同,仅需注意最后添加节点信息的顺序问题。

class Solution:
    def levelOrderBottom(self, root: TreeNode) -> list:
        # solution 1:队列。采用BFS时,首先应该考虑的就是遍历树,通过构建一个队列去承载树每层的节点,再根据队列提取我们需要的
        # 节点信息。

        # special considerations:
        if root is None:
            return []

        # parameters
        ans = []
        cur_level = [root]

        while cur_level:
            next_level = []
            temp = []  # get the values of each node on the same level

            for node in cur_level:
                temp.append(node.val)
                if node.left is not None:
                    next_level.append(node.left)
                if node.right is not None:
                    next_level.append(node.right)
            
            # insert the node info to the first position
            ans.insert(0, temp)
            cur_level = next_level

        return ans

Binary Tree Zigzag Level Order Traversal

Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).

For example:
Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
return its zigzag level order traversal as:

[
  [3],
  [20,9],
  [15,7]
]

ZigZag在这里也和上一题的解法一致,同样只需注意最后添加信息的顺序问题。

class Solution:
    def zigzagLevelOrder(self, root: TreeNode) -> list:
        # solution: 类似于普通的层序遍历。区别就是遍历时需注意每层提取的节点顺序不同。

        # special considerations
        if root is None:
            return []

        # parameters
        ans = []
        cur_level = [root]  # define the elements in each level
        left_right = True

        while cur_level:
            next_level = []  # define next level
            values = []  # define the values of each element in the same level

            # essential: use 'for' loop to get each node info
            for node in cur_level:
                values.append(node.val)

                if node.left is not None:
                    next_level.append(node.left)
                if node.right is not None:
                    next_level.append(node.right)

            if left_right:
                ans.append(values)
                left_right = False
            else:
                values = values[::-1]
                ans.append(values)
                left_right = True

            cur_level = next_level

        return ans

 

Binary Tree Right Side View

Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom.

Example:

Input: [1,2,3,null,5,null,4]
Output: [1, 3, 4]
Explanation:

   1            <---
 /   \
2     3         <---
 \     \
  5     4       <---

首先创建一个队列用于承载树每层的节点。遍历树,每次只取队列最尾部的节点的值,然后找到队列里所有节点的子节点,按照从左到右的顺序依次装入新的队列里,再循环。直到队列没有节点可装为止。

我一开始做这道题就想着通过观察法一层一层地寻找最右边的节点,但这样无非就是给自己挖坑,细节顾不过来。正确的方法是,利用队列 (数据结构)的构造,将树每层的节点从左到右依次排放在一个队列里,这样就能迅速定位最右边的节点。做题时不能想着观察法,还是得用数据结构这个工具去辅助,才能达到高效率。

class Solution:
    def rightSideView(self, root: TreeNode) -> list:
        # solution: 队列+迭代。

        # special consideration
        if root is None:
            return []

        # parameters
        ans = []
        queue = [root]

        while len(queue) != 0:
            next_level_queue = []
            # only get the last node's value (critical)
            ans.append(queue[-1].val)

            # get the next generation
            for node in queue:
                # pay attention to the order of putting nodes
                if node.left is not None:
                    next_level_queue.append(node.left)
                if node.right is not None:
                    next_level_queue.append(node.right)

            queue = next_level_queue

        return ans

Minimum Depth of Binary Tree

Given a binary tree, find its minimum depth.
The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.
Note: A leaf is a node with no children.

Example:

Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
return its minimum depth = 2.

此题使用了DFS(深度优先),作为下一期训练的预热吧。这里我运用了递归,需要注意的是寻找循环的终止条件(当前节点无子节点)和层与层之间的规律(只要有子节点,就把它们当成下一次递归的参数,继续循环下去)。需要注意的是,得优先考虑节点的左右孩子都不为空的情况,这样可以避免先考虑左/右孩子而导致情况不全的问题。

class Solution:
    def minDepth(self, root: TreeNode) -> int:
        # solution: 递归。

        # special consideration
        if root is None:
            return 0

        # base
        if root.left is None and root.right is None:
            return 1

        # patterns between layers
        if root.left is not None and root.right is not None:
            return min(1 + self.minDepth(root.left), 1 + self.minDepth(root.right))
        if root.left is None:
            return 1 + self.minDepth(root.right)
        if root.right is None:
            return 1 + self.minDepth(root.left)

 

总结

在我练完上面的题后,发现树上的BFS解题套路特别明显:借助队列。凡是涉及到树的层次遍历,都逃不了使用队列这个数据结构去装载它每层的节点信息。以后遇到类似的题型时,可以优先考虑运用队列思想。

 

如果笔记存在一些问题,发现后我会尽快纠正。

*注:本文的所有题目均来源于leetcode

你可能感兴趣的:(leetcode训练,数据结构与算法)