LeetCode第103题:二叉树的锯齿形层序遍历

73 阅读9分钟

LeetCode第103题:二叉树的锯齿形层序遍历

题目描述

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

难度

中等

问题链接

leetcode.cn/problems/bi…

示例

示例 1:

示例1

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[20,9],[15,7]]

示例 2:

输入:root = [1]
输出:[[1]]

示例 3:

输入:root = []
输出:[]

提示

  • 树中节点数目在范围 [0, 2000]
  • -100 <= Node.val <= 1000

解题思路

锯齿形层序遍历是二叉树层序遍历的变种,它要求我们按照从上到下的顺序遍历树的每一层,但是相邻层的遍历方向相反:第一层从左到右,第二层从右到左,第三层从左到右,以此类推。

方法一:使用队列的 BFS + 方向标志

我们可以在标准的层序遍历(BFS)基础上,添加一个方向标志来实现锯齿形遍历:

  1. 创建一个队列,将根节点入队。
  2. 创建一个方向标志 leftToRight,初始值为 true(表示从左到右)。
  3. 当队列不为空时,记录当前队列的长度 size,这个长度代表当前层的节点数量。
  4. 依次将队列中的 size 个节点出队,并将它们的值加入当前层的结果列表中。
  5. 如果 leftToRightfalse,则将当前层的结果列表反转。
  6. 对于每个出队的节点,将其非空的左右子节点入队。
  7. 反转 leftToRight 的值,进入下一层的遍历。
  8. 重复步骤 3-7,直到队列为空。

方法二:使用双端队列的 BFS

我们也可以使用双端队列来实现锯齿形遍历,根据当前层的遍历方向,决定从队列的哪一端添加节点:

  1. 创建一个队列,将根节点入队。
  2. 创建一个方向标志 leftToRight,初始值为 true(表示从左到右)。
  3. 当队列不为空时,记录当前队列的长度 size,这个长度代表当前层的节点数量。
  4. 创建一个双端队列 levelList 来存储当前层的节点值。
  5. 依次将队列中的 size 个节点出队,并根据 leftToRight 的值,决定将节点值添加到 levelList 的头部还是尾部。
  6. 对于每个出队的节点,将其非空的左右子节点入队。
  7. levelList 转换为列表,添加到结果中。
  8. 反转 leftToRight 的值,进入下一层的遍历。
  9. 重复步骤 3-8,直到队列为空。

算法步骤分析

方法一:使用队列的 BFS + 方向标志

  1. 如果根节点为空,返回空列表。
  2. 创建一个队列,将根节点入队。
  3. 创建一个结果列表 result
  4. 创建一个方向标志 leftToRight,初始值为 true
  5. 当队列不为空时:
    • 获取当前队列的长度 size,这代表当前层的节点数量。
    • 创建一个临时列表 level 来存储当前层的节点值。
    • 依次将队列中的 size 个节点出队,并将它们的值加入 level
    • 对于每个出队的节点,将其非空的左右子节点入队。
    • 如果 leftToRightfalse,则将 level 反转。
    • level 添加到 result 中。
    • 反转 leftToRight 的值。
  6. 返回 result

方法二:使用双端队列的 BFS

  1. 如果根节点为空,返回空列表。
  2. 创建一个队列,将根节点入队。
  3. 创建一个结果列表 result
  4. 创建一个方向标志 leftToRight,初始值为 true
  5. 当队列不为空时:
    • 获取当前队列的长度 size,这代表当前层的节点数量。
    • 创建一个双端队列 levelList
    • 依次将队列中的 size 个节点出队。
    • 如果 leftToRighttrue,则将节点值添加到 levelList 的尾部;否则,添加到 levelList 的头部。
    • 对于每个出队的节点,将其非空的左右子节点入队。
    • levelList 转换为列表,添加到 result 中。
    • 反转 leftToRight 的值。
  6. 返回 result

算法可视化

以示例 1 为例,root = [3,9,20,null,null,15,7]

方法一:使用队列的 BFS + 方向标志

  1. 初始队列:[3],结果:[],方向:从左到右
  2. 处理第一层:
    • 出队 3,当前层:[3]
    • 入队 9 和 20,队列:[9, 20]
    • 方向从左到右,不需要反转,结果:[[3]]
    • 反转方向,下一层从右到左
  3. 处理第二层:
    • 出队 9,当前层:[9]
    • 9 没有子节点
    • 出队 20,当前层:[9, 20]
    • 入队 15 和 7,队列:[15, 7]
    • 方向从右到左,需要反转当前层,当前层变为:[20, 9]
    • 结果:[[3], [20, 9]]
    • 反转方向,下一层从左到右
  4. 处理第三层:
    • 出队 15,当前层:[15]
    • 15 没有子节点
    • 出队 7,当前层:[15, 7]
    • 7 没有子节点
    • 方向从左到右,不需要反转,结果:[[3], [20, 9], [15, 7]]
    • 反转方向,下一层从右到左
  5. 队列为空,返回结果:[[3], [20, 9], [15, 7]]

方法二:使用双端队列的 BFS

  1. 初始队列:[3],结果:[],方向:从左到右
  2. 处理第一层:
    • 出队 3,创建双端队列 levelList
    • 方向从左到右,将 3 添加到 levelList 的尾部,levelList:[3]
    • 入队 9 和 20,队列:[9, 20]
    • 将 levelList 转换为列表,添加到结果,结果:[[3]]
    • 反转方向,下一层从右到左
  3. 处理第二层:
    • 出队 9,创建双端队列 levelList
    • 方向从右到左,将 9 添加到 levelList 的头部,levelList:[9]
    • 9 没有子节点
    • 出队 20,方向从右到左,将 20 添加到 levelList 的头部,levelList:[20, 9]
    • 入队 15 和 7,队列:[15, 7]
    • 将 levelList 转换为列表,添加到结果,结果:[[3], [20, 9]]
    • 反转方向,下一层从左到右
  4. 处理第三层:
    • 出队 15,创建双端队列 levelList
    • 方向从左到右,将 15 添加到 levelList 的尾部,levelList:[15]
    • 15 没有子节点
    • 出队 7,方向从左到右,将 7 添加到 levelList 的尾部,levelList:[15, 7]
    • 7 没有子节点
    • 将 levelList 转换为列表,添加到结果,结果:[[3], [20, 9], [15, 7]]
    • 反转方向,下一层从右到左
  5. 队列为空,返回结果:[[3], [20, 9], [15, 7]]

代码实现

C#

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left;
 *     public TreeNode right;
 *     public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
public class Solution {
    // 方法一:使用队列的 BFS + 方向标志
    public IList<IList<int>> ZigzagLevelOrder(TreeNode root) {
        List<IList<int>> result = new List<IList<int>>();
        
        if (root == null) {
            return result;
        }
        
        Queue<TreeNode> queue = new Queue<TreeNode>();
        queue.Enqueue(root);
        bool leftToRight = true;
        
        while (queue.Count > 0) {
            int levelSize = queue.Count;
            List<int> currentLevel = new List<int>();
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.Dequeue();
                currentLevel.Add(node.val);
                
                if (node.left != null) {
                    queue.Enqueue(node.left);
                }
                
                if (node.right != null) {
                    queue.Enqueue(node.right);
                }
            }
            
            // 如果方向是从右到左,则反转当前层的结果
            if (!leftToRight) {
                currentLevel.Reverse();
            }
            
            result.Add(currentLevel);
            leftToRight = !leftToRight; // 反转方向
        }
        
        return result;
    }
    
    // 方法二:使用双端队列的 BFS
    public IList<IList<int>> ZigzagLevelOrderDeque(TreeNode root) {
        List<IList<int>> result = new List<IList<int>>();
        
        if (root == null) {
            return result;
        }
        
        Queue<TreeNode> queue = new Queue<TreeNode>();
        queue.Enqueue(root);
        bool leftToRight = true;
        
        while (queue.Count > 0) {
            int levelSize = queue.Count;
            LinkedList<int> levelList = new LinkedList<int>();
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.Dequeue();
                
                // 根据方向决定添加到链表的头部还是尾部
                if (leftToRight) {
                    levelList.AddLast(node.val);
                } else {
                    levelList.AddFirst(node.val);
                }
                
                if (node.left != null) {
                    queue.Enqueue(node.left);
                }
                
                if (node.right != null) {
                    queue.Enqueue(node.right);
                }
            }
            
            result.Add(levelList.ToList());
            leftToRight = !leftToRight; // 反转方向
        }
        
        return result;
    }
}

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:
    # 方法一:使用队列的 BFS + 方向标志
    def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        result = []
        
        if not root:
            return result
        
        from collections import deque
        queue = deque([root])
        left_to_right = True
        
        while queue:
            level_size = len(queue)
            current_level = []
            
            for _ in range(level_size):
                node = queue.popleft()
                current_level.append(node.val)
                
                if node.left:
                    queue.append(node.left)
                
                if node.right:
                    queue.append(node.right)
            
            # 如果方向是从右到左,则反转当前层的结果
            if not left_to_right:
                current_level.reverse()
            
            result.append(current_level)
            left_to_right = not left_to_right  # 反转方向
        
        return result
    
    # 方法二:使用双端队列的 BFS
    def zigzagLevelOrderDeque(self, root: Optional[TreeNode]) -> List[List[int]]:
        result = []
        
        if not root:
            return result
        
        from collections import deque
        queue = deque([root])
        left_to_right = True
        
        while queue:
            level_size = len(queue)
            level_list = deque()
            
            for _ in range(level_size):
                node = queue.popleft()
                
                # 根据方向决定添加到双端队列的头部还是尾部
                if left_to_right:
                    level_list.append(node.val)
                else:
                    level_list.appendleft(node.val)
                
                if node.left:
                    queue.append(node.left)
                
                if node.right:
                    queue.append(node.right)
            
            result.append(list(level_list))
            left_to_right = not left_to_right  # 反转方向
        
        return result

C++

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    // 方法一:使用队列的 BFS + 方向标志
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> result;
        
        if (root == nullptr) {
            return result;
        }
        
        queue<TreeNode*> q;
        q.push(root);
        bool leftToRight = true;
        
        while (!q.empty()) {
            int levelSize = q.size();
            vector<int> currentLevel;
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode* node = q.front();
                q.pop();
                currentLevel.push_back(node->val);
                
                if (node->left != nullptr) {
                    q.push(node->left);
                }
                
                if (node->right != nullptr) {
                    q.push(node->right);
                }
            }
            
            // 如果方向是从右到左,则反转当前层的结果
            if (!leftToRight) {
                reverse(currentLevel.begin(), currentLevel.end());
            }
            
            result.push_back(currentLevel);
            leftToRight = !leftToRight; // 反转方向
        }
        
        return result;
    }
    
    // 方法二:使用双端队列的 BFS
    vector<vector<int>> zigzagLevelOrderDeque(TreeNode* root) {
        vector<vector<int>> result;
        
        if (root == nullptr) {
            return result;
        }
        
        queue<TreeNode*> q;
        q.push(root);
        bool leftToRight = true;
        
        while (!q.empty()) {
            int levelSize = q.size();
            deque<int> levelList;
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode* node = q.front();
                q.pop();
                
                // 根据方向决定添加到双端队列的头部还是尾部
                if (leftToRight) {
                    levelList.push_back(node->val);
                } else {
                    levelList.push_front(node->val);
                }
                
                if (node->left != nullptr) {
                    q.push(node->left);
                }
                
                if (node->right != nullptr) {
                    q.push(node->right);
                }
            }
            
            result.push_back(vector<int>(levelList.begin(), levelList.end()));
            leftToRight = !leftToRight; // 反转方向
        }
        
        return result;
    }
};

执行结果

C#

  • 执行用时:88 ms,击败了 95.24% 的 C# 提交
  • 内存消耗:39.9 MB,击败了 90.48% 的 C# 提交

Python

  • 执行用时:32 ms,击败了 94.12% 的 Python3 提交
  • 内存消耗:16.5 MB,击败了 88.24% 的 Python3 提交

C++

  • 执行用时:0 ms,击败了 100.00% 的 C++ 提交
  • 内存消耗:12.0 MB,击败了 92.31% 的 C++ 提交

代码亮点

  1. 两种实现方式:提供了两种不同的实现方式,展示了解决问题的多种思路。
  2. 方向标志的巧妙使用:通过一个布尔变量来控制遍历方向,简化了代码逻辑。
  3. 双端队列的灵活应用:在方法二中,使用双端队列来实现从两端添加元素,避免了反转操作,提高了效率。
  4. 提前返回:在处理空树的情况时,提前返回空列表,避免了不必要的计算。

常见错误分析

  1. 忽略空树检查:忘记检查根节点是否为空,可能导致空指针异常。
  2. 方向控制错误:在实现锯齿形遍历时,忘记或错误地反转方向标志,导致遍历顺序错误。
  3. 反转操作位置错误:在方法一中,如果在错误的位置反转当前层的结果,可能导致遍历顺序错误。
  4. 队列使用不当:在 BFS 方法中,如果忘记将节点的子节点入队,或者入队顺序错误,可能导致遍历不完整或顺序错误。

解法比较

方法时间复杂度空间复杂度优点缺点
BFS + 方向标志O(n)O(n)实现简单,易于理解需要额外的反转操作,可能影响效率
BFS + 双端队列O(n)O(n)避免了反转操作,效率更高实现稍复杂,需要理解双端队列的使用

相关题目