LeetCode第101题:对称二叉树

77 阅读8分钟

LeetCode第101题:对称二叉树

题目描述

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

难度

简单

问题链接

leetcode.cn/problems/sy…

示例

示例 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. 如果 leftright 都为 null,返回 true
  3. 如果 leftright 其中一个为 null,返回 false
  4. 如果 left.val 不等于 right.val,返回 false
  5. 递归判断 left.leftright.right 是否对称,以及 left.rightright.left 是否对称。
  6. 如果上述两个条件都满足,返回 true,否则返回 false

迭代方法(使用队列):

  1. 创建一个队列,将根节点的左右子节点入队。
  2. 当队列不为空时:
    • 取出队首的两个节点 leftright
    • 如果 leftright 都为 null,继续下一轮循环。
    • 如果 leftright 其中一个为 null,或者 left.val 不等于 right.val,返回 false
    • left.leftright.right 入队。
    • left.rightright.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.leftroot.right 入队:队列 = [2, 2]。
  2. 取出 22,检查 2 == 2,条件满足。
  3. 2.left2.right 入队,以及 2.right2.left 入队:队列 = [3, 3, 4, 4]。
  4. 取出 33,检查 3 == 3,条件满足。
  5. 3.left3.right 入队,以及 3.right3.left 入队:队列 = [4, 4, null, null, null, null]。
  6. 取出 44,检查 4 == 4,条件满足。
  7. 4.left4.right 入队,以及 4.right4.left 入队:队列 = [null, null, null, null, null, null, null, null]。
  8. 取出 nullnull,继续循环。
  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)避免了递归可能导致的栈溢出问题实现稍复杂,需要额外的队列空间

相关题目