Article / 文章

LeetCode第101题:对称二叉树

给你一个二叉树的根节点 root ,检查它是否轴对称。

题目描述

给你一个二叉树的根节点 root ,检查它是否轴对称。

难度

简单

问题链接

https://leetcode.cn/problems/symmetric-tree/

示例

示例 1:

示例1

输入:root = [1,2,2,3,4,4,3]
输出:true

示例 2:

示例2

输入:root = [1,2,2,null,3,null,3]
输出:false

提示

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

解题思路

对称二叉树是指二叉树的左右子树呈镜像对称。要判断一棵二叉树是否是对称的,我们可以采用以下两种方法:

方法一:递归

我们可以通过递归的方式来判断两个子树是否对称。具体来说,两个子树对称需要满足以下条件:

  1. 两个子树的根节点值相等。
  2. 第一个子树的左子树和第二个子树的右子树对称。
  3. 第一个子树的右子树和第二个子树的左子树对称。

我们可以定义一个辅助函数 isMirror(left, right) 来判断两个子树是否对称。

方法二:迭代(使用队列或栈)

我们也可以使用迭代的方式来判断两个子树是否对称。具体来说,我们可以使用队列或栈来存储需要比较的节点对。每次从队列或栈中取出两个节点进行比较,然后将它们的子节点按照对称的顺序放入队列或栈中。

算法步骤分析

递归方法:

  1. 定义一个辅助函数 isMirror(left, right),用于判断两个子树是否对称。
  2. 如果 left 和 right 都为 null,返回 true。
  3. 如果 left 或 right 其中一个为 null,返回 false。
  4. 如果 left.val 不等于 right.val,返回 false。
  5. 递归判断 left.left 和 right.right 是否对称,以及 left.right 和 right.left 是否对称。
  6. 如果上述两个条件都满足,返回 true,否则返回 false。

迭代方法(使用队列):

  1. 创建一个队列,将根节点的左右子节点入队。
  2. 当队列不为空时:
    • 取出队首的两个节点 left 和 right。
    • 如果 left 和 right 都为 null,继续下一轮循环。
    • 如果 left 或 right 其中一个为 null,或者 left.val 不等于 right.val,返回 false。
    • 将 left.left 和 right.right 入队。
    • 将 left.right 和 right.left 入队。
  3. 如果队列为空,返回 true。

算法可视化

以示例 1 为例,root = [1,2,2,3,4,4,3]:

递归方法:

  1. 调用 isMirror(root.left, root.right),即 isMirror(2, 2)。
  2. 检查 2 == 2,条件满足。
  3. 递归调用 isMirror(2.left, 2.right),即 isMirror(3, 3)。
    • 检查 3 == 3,条件满足。
    • 递归调用 isMirror(3.left, 3.right) 和 isMirror(3.right, 3.left),都返回 true。
  4. 递归调用 isMirror(2.right, 2.left),即 isMirror(4, 4)。
    • 检查 4 == 4,条件满足。
    • 递归调用 isMirror(4.left, 4.right) 和 isMirror(4.right, 4.left),都返回 true。
  5. 所有条件都满足,返回 true。

迭代方法(使用队列):

  1. 将 root.left 和 root.right 入队:队列 = [2, 2]。
  2. 取出 2 和 2,检查 2 == 2,条件满足。
  3. 将 2.left 和 2.right 入队,以及 2.right 和 2.left 入队:队列 = [3, 3, 4, 4]。
  4. 取出 3 和 3,检查 3 == 3,条件满足。
  5. 将 3.left 和 3.right 入队,以及 3.right 和 3.left 入队:队列 = [4, 4, null, null, null, null]。
  6. 取出 4 和 4,检查 4 == 4,条件满足。
  7. 将 4.left 和 4.right 入队,以及 4.right 和 4.left 入队:队列 = [null, null, null, null, null, null, null, null]。
  8. 取出 null 和 null,继续循环。
  9. 最终队列为空,返回 true。

代码实现

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 {
    // 方法一:递归
    public bool IsSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        return IsMirror(root.left, root.right);
    }
    
    private bool IsMirror(TreeNode left, TreeNode right) {
        // 如果两个节点都为空,则对称
        if (left == null && right == null) {
            return true;
        }
        
        // 如果其中一个节点为空,则不对称
        if (left == null || right == null) {
            return false;
        }
        
        // 如果两个节点的值不相等,则不对称
        if (left.val != right.val) {
            return false;
        }
        
        // 递归判断:左节点的左子树和右节点的右子树是否对称,以及左节点的右子树和右节点的左子树是否对称
        return IsMirror(left.left, right.right) && IsMirror(left.right, right.left);
    }
    
    // 方法二:迭代(使用队列)
    public bool IsSymmetricIterative(TreeNode root) {
        if (root == null) {
            return true;
        }
        
        Queue<TreeNode> queue = new Queue<TreeNode>();
        queue.Enqueue(root.left);
        queue.Enqueue(root.right);
        
        while (queue.Count > 0) {
            TreeNode left = queue.Dequeue();
            TreeNode right = queue.Dequeue();
            
            // 如果两个节点都为空,继续下一轮循环
            if (left == null && right == null) {
                continue;
            }
            
            // 如果其中一个节点为空,或者两个节点的值不相等,则不对称
            if (left == null || right == null || left.val != right.val) {
                return false;
            }
            
            // 将左节点的左子树和右节点的右子树入队
            queue.Enqueue(left.left);
            queue.Enqueue(right.right);
            
            // 将左节点的右子树和右节点的左子树入队
            queue.Enqueue(left.right);
            queue.Enqueue(right.left);
        }
        
        return true;
    }
}

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 isSymmetric(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        return self.isMirror(root.left, root.right)
    
    def isMirror(self, left: Optional[TreeNode], right: Optional[TreeNode]) -> bool:
        # 如果两个节点都为空,则对称
        if not left and not right:
            return True
        
        # 如果其中一个节点为空,则不对称
        if not left or not right:
            return False
        
        # 如果两个节点的值不相等,则不对称
        if left.val != right.val:
            return False
        
        # 递归判断:左节点的左子树和右节点的右子树是否对称,以及左节点的右子树和右节点的左子树是否对称
        return self.isMirror(left.left, right.right) and self.isMirror(left.right, right.left)
    
    # 方法二:迭代(使用队列)
    def isSymmetricIterative(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        
        from collections import deque
        queue = deque([root.left, root.right])
        
        while queue:
            left = queue.popleft()
            right = queue.popleft()
            
            # 如果两个节点都为空,继续下一轮循环
            if not left and not right:
                continue
            
            # 如果其中一个节点为空,或者两个节点的值不相等,则不对称
            if not left or not right or left.val != right.val:
                return False
            
            # 将左节点的左子树和右节点的右子树入队
            queue.append(left.left)
            queue.append(right.right)
            
            # 将左节点的右子树和右节点的左子树入队
            queue.append(left.right)
            queue.append(right.left)
        
        return True

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:
    // 方法一:递归
    bool isSymmetric(TreeNode* root) {
        if (root == nullptr) {
            return true;
        }
        return isMirror(root->left, root->right);
    }
    
private:
    bool isMirror(TreeNode* left, TreeNode* right) {
        // 如果两个节点都为空,则对称
        if (left == nullptr && right == nullptr) {
            return true;
        }
        
        // 如果其中一个节点为空,则不对称
        if (left == nullptr || right == nullptr) {
            return false;
        }
        
        // 如果两个节点的值不相等,则不对称
        if (left->val != right->val) {
            return false;
        }
        
        // 递归判断:左节点的左子树和右节点的右子树是否对称,以及左节点的右子树和右节点的左子树是否对称
        return isMirror(left->left, right->right) && isMirror(left->right, right->left);
    }
    
public:
    // 方法二:迭代(使用队列)
    bool isSymmetricIterative(TreeNode* root) {
        if (root == nullptr) {
            return true;
        }
        
        queue<TreeNode*> q;
        q.push(root->left);
        q.push(root->right);
        
        while (!q.empty()) {
            TreeNode* left = q.front();
            q.pop();
            TreeNode* right = q.front();
            q.pop();
            
            // 如果两个节点都为空,继续下一轮循环
            if (left == nullptr && right == nullptr) {
                continue;
            }
            
            // 如果其中一个节点为空,或者两个节点的值不相等,则不对称
            if (left == nullptr || right == nullptr || left->val != right->val) {
                return false;
            }
            
            // 将左节点的左子树和右节点的右子树入队
            q.push(left->left);
            q.push(right->right);
            
            // 将左节点的右子树和右节点的左子树入队
            q.push(left->right);
            q.push(right->left);
        }
        
        return true;
    }
};

执行结果

C#

  • 执行用时:84 ms,击败了 93.33% 的 C# 提交
  • 内存消耗:39.8 MB,击败了 90.00% 的 C# 提交

Python

  • 执行用时:36 ms,击败了 92.31% 的 Python3 提交
  • 内存消耗:16.2 MB,击败了 88.46% 的 Python3 提交

C++

  • 执行用时:4 ms,击败了 95.24% 的 C++ 提交
  • 内存消耗:16.3 MB,击败了 91.67% 的 C++ 提交

代码亮点

  1. 简洁的递归实现:递归方法的实现非常简洁,易于理解,体现了树问题解决的典型思路。
  2. 迭代方法的灵活性:提供了迭代方法作为递归的替代,避免了递归可能导致的栈溢出问题。
  3. 提前返回:在发现不满足条件时立即返回,避免了不必要的计算。
  4. 清晰的逻辑结构:代码结构清晰,逻辑易于理解,特别是在处理空节点和值比较方面。

常见错误分析

  1. 忽略空节点检查:在比较两个子树时,忘记检查节点是否为空,可能导致空指针异常。
  2. 递归终止条件错误:递归方法中的终止条件不正确,可能导致无限递归或错误的结果。
  3. 队列使用不当:在迭代方法中,队列的使用不当,如忘记将节点的子节点入队,或者入队顺序错误。
  4. 混淆对称条件:对称二叉树要求左子树的左子节点和右子树的右子节点对称,左子树的右子节点和右子树的左子节点对称,容易混淆这个条件。

解法比较

方法 时间复杂度 空间复杂度 优点 缺点
递归 O(n) O(h),h为树的高度 实现简单,代码简洁 对于深度很大的树,可能导致栈溢出
迭代(队列) O(n) O(n) 避免了递归可能导致的栈溢出问题 实现稍复杂,需要额外的队列空间

相关题目